Skip to content

Commit ea9a42d

Browse files
committed
Bump version to 0.3.0 for release
1 parent aaab1a3 commit ea9a42d

File tree

2 files changed

+204
-1
lines changed

2 files changed

+204
-1
lines changed

examples/05_stock_monitor_ui.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import asyncio
2+
import random
3+
import time
4+
from dataclasses import dataclass
5+
from typing import Dict, Optional
6+
from tsignal import t_with_signals, t_signal, t_slot, t_with_worker
7+
import logging
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
@dataclass
13+
class StockPrice:
14+
code: str
15+
price: float
16+
change: float
17+
timestamp: float
18+
19+
20+
@t_with_worker
21+
class StockService:
22+
"""가상의 주식 시세 데이터를 생성하고 전달하는 서비스"""
23+
24+
def __init__(self):
25+
self.prices: Dict[str, float] = {
26+
"AAPL": 180.0,
27+
"GOOGL": 140.0,
28+
"MSFT": 370.0,
29+
}
30+
self.last_prices = self.prices.copy()
31+
self._running = False
32+
super().__init__()
33+
34+
@t_signal
35+
def price_updated(self):
36+
"""주식 시세가 업데이트될 때 발생하는 시그널"""
37+
pass
38+
39+
async def initialize(self):
40+
"""워커 초기화"""
41+
logging.debug("StockService 초기화 시작")
42+
self._running = True
43+
await self.update_prices()
44+
logging.debug("StockService 초기화 완료")
45+
46+
async def finalize(self):
47+
"""워커 종료"""
48+
logging.debug("StockService 종료 시작")
49+
self._running = False
50+
logging.debug("StockService 종료 완료")
51+
52+
async def update_prices(self):
53+
"""주기적으로 시세 업데이트"""
54+
logging.debug("주식 가격 업데이트 시작")
55+
while self._running:
56+
for code in self.prices:
57+
# 이전 가격 저장
58+
self.last_prices[code] = self.prices[code]
59+
60+
# 가격 변동 시뮬레이션 (-1% ~ +1%)
61+
change_pct = random.uniform(-0.01, 0.01)
62+
self.prices[code] *= 1 + change_pct
63+
64+
# 시세 데이터 생성 및 시그널 발생
65+
price_data = StockPrice(
66+
code=code,
67+
price=self.prices[code],
68+
change=((self.prices[code] / self.last_prices[code]) - 1) * 100,
69+
timestamp=time.time(),
70+
)
71+
logging.debug(
72+
f"{code} 가격 업데이트: {price_data.price:.2f} (변동: {price_data.change:.2f}%)"
73+
)
74+
self.price_updated.emit(price_data)
75+
76+
await asyncio.sleep(1) # 1초마다 업데이트
77+
78+
79+
@t_with_worker
80+
class StockProcessor:
81+
"""시세 데이터를 처리하고 알림 조건을 체크하는 프로세서"""
82+
83+
def __init__(self):
84+
self.price_alerts: Dict[str, tuple[Optional[float], Optional[float]]] = {}
85+
super().__init__()
86+
87+
@t_signal
88+
def alert_triggered(self):
89+
"""가격 알림 조건이 충족되면 발생하는 시그널"""
90+
pass
91+
92+
@t_signal
93+
def price_processed(self):
94+
"""시세 데이터 처리가 완료되면 발생하는 시그널"""
95+
pass
96+
97+
async def initialize(self):
98+
"""워커 초기화"""
99+
pass
100+
101+
async def finalize(self):
102+
"""워커 종료"""
103+
self.price_alerts.clear()
104+
105+
@t_slot
106+
async def on_price_updated(self, price_data: StockPrice):
107+
"""StockService로부터 시세 업데이트를 수신"""
108+
# 알림 조건 체크 태스크 큐잉
109+
await self.queue_task(self.process_price(price_data))
110+
111+
async def process_price(self, price_data: StockPrice):
112+
"""시세 데이터 처리"""
113+
logging.debug(f"{price_data.code} 가격 처리: {price_data.price:.2f}")
114+
# 알림 조건 체크
115+
if price_data.code in self.price_alerts:
116+
lower, upper = self.price_alerts[price_data.code]
117+
if lower and price_data.price <= lower:
118+
logging.debug(
119+
f"{price_data.code} 가격 알림 트리거: LOW ({price_data.price:.2f})"
120+
)
121+
self.alert_triggered.emit(price_data.code, "LOW", price_data.price)
122+
if upper and price_data.price >= upper:
123+
logging.debug(
124+
f"{price_data.code} 가격 알림 트리거: HIGH ({price_data.price:.2f})"
125+
)
126+
self.alert_triggered.emit(price_data.code, "HIGH", price_data.price)
127+
128+
# 처리된 데이터 전달
129+
self.price_processed.emit(price_data)
130+
131+
def set_price_alert(
132+
self, code: str, lower: Optional[float] = None, upper: Optional[float] = None
133+
):
134+
"""가격 알림 설정"""
135+
self.price_alerts[code] = (lower, upper)
136+
137+
138+
@t_with_signals
139+
class StockViewModel:
140+
"""UI 상태를 관리하는 뷰모델"""
141+
142+
def __init__(self):
143+
self.current_prices: Dict[str, StockPrice] = {}
144+
self.alerts: list[tuple[str, str, float]] = []
145+
146+
@t_signal
147+
def prices_updated(self):
148+
"""시세 데이터가 업데이트되면 발생하는 시그널"""
149+
pass
150+
151+
@t_signal
152+
def alert_added(self):
153+
"""새로운 알림이 추가되면 발생하는 시그널"""
154+
pass
155+
156+
@t_slot
157+
def on_price_processed(self, price_data: StockPrice):
158+
"""StockProcessor로부터 처리된 시세 데이터를 수신"""
159+
self.current_prices[price_data.code] = price_data
160+
self.prices_updated.emit(dict(self.current_prices))
161+
162+
@t_slot
163+
def on_alert_triggered(self, code: str, alert_type: str, price: float):
164+
"""StockProcessor로부터 알림 트리거를 수신"""
165+
self.alerts.append((code, alert_type, price))
166+
self.alert_added.emit(code, alert_type, price)
167+
168+
169+
async def main():
170+
"""테스트를 위한 메인 함수"""
171+
172+
root = logging.getLogger()
173+
default_level = logging.DEBUG
174+
root.setLevel(default_level)
175+
176+
# 컴포넌트 생성
177+
service = StockService()
178+
processor = StockProcessor()
179+
viewmodel = StockViewModel()
180+
181+
# 시그널 연결
182+
service.price_updated.connect(processor, processor.on_price_updated)
183+
processor.price_processed.connect(viewmodel, viewmodel.on_price_processed)
184+
processor.alert_triggered.connect(viewmodel, viewmodel.on_alert_triggered)
185+
186+
# 워커 시작
187+
service.start()
188+
processor.start()
189+
190+
# 알림 설정 예시
191+
processor.set_price_alert("AAPL", lower=175.0, upper=185.0)
192+
193+
try:
194+
# 테스트를 위해 잠시 실행
195+
await asyncio.sleep(10)
196+
finally:
197+
# 워커 종료
198+
service.stop()
199+
processor.stop()
200+
201+
202+
if __name__ == "__main__":
203+
asyncio.run(main())

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "tsignal"
7-
version = "0.2.1"
7+
version = "0.3.0"
88
description = "A Python Signal-Slot library inspired by Qt"
99
readme = "README.md"
1010
requires-python = ">=3.10"

0 commit comments

Comments
 (0)