db2_hj3415 0.1.1__py2.py3-none-any.whl → 0.1.3__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- db2_hj3415/common/connection.py +13 -1
- db2_hj3415/common/utils.py +7 -1
- db2_hj3415/mi/__init__.py +3 -1
- db2_hj3415/nfs/__init__.py +5 -1
- db2_hj3415/nfs/_c10346.py +18 -28
- db2_hj3415/nfs/c101.py +32 -12
- db2_hj3415/nfs/models.py +74 -0
- db2_hj3415/redis_manager.py +16 -0
- {db2_hj3415-0.1.1.dist-info → db2_hj3415-0.1.3.dist-info}/METADATA +3 -1
- {db2_hj3415-0.1.1.dist-info → db2_hj3415-0.1.3.dist-info}/RECORD +12 -11
- db2_hj3415/redis/__init__.py +0 -0
- {db2_hj3415-0.1.1.dist-info → db2_hj3415-0.1.3.dist-info}/WHEEL +0 -0
- {db2_hj3415-0.1.1.dist-info → db2_hj3415-0.1.3.dist-info}/entry_points.txt +0 -0
db2_hj3415/common/connection.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# MongoDB/Redis 연결
|
2
2
|
import os
|
3
3
|
from motor.motor_asyncio import AsyncIOMotorClient
|
4
|
+
import redis.asyncio as redis
|
5
|
+
from redis.asyncio.client import Redis
|
4
6
|
|
5
7
|
# 싱글톤 몽고 클라이언트 정의
|
6
8
|
MONGO_URI = os.getenv("MONGO_ADDR", "mongodb://localhost:27017")
|
@@ -14,4 +16,14 @@ def get_mongo_client() -> AsyncIOMotorClient:
|
|
14
16
|
|
15
17
|
def close_mongo_client():
|
16
18
|
if client:
|
17
|
-
client.close()
|
19
|
+
client.close()
|
20
|
+
|
21
|
+
|
22
|
+
async def get_redis() -> Redis:
|
23
|
+
client = redis.Redis(host="localhost", port=6379, decode_responses=True)
|
24
|
+
try:
|
25
|
+
await client.ping()
|
26
|
+
return client
|
27
|
+
except Exception as e:
|
28
|
+
print("Redis connection failed:", e)
|
29
|
+
return None
|
db2_hj3415/common/utils.py
CHANGED
db2_hj3415/mi/__init__.py
CHANGED
db2_hj3415/nfs/__init__.py
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
DB_NAME = "nfs"
|
2
2
|
DATE_FORMAT = "%Y.%m.%d"
|
3
3
|
|
4
|
-
from db2_hj3415.common import connection
|
4
|
+
from db2_hj3415.common import connection
|
5
|
+
from db2_hj3415.common.utils import *
|
6
|
+
from db2_hj3415.common.db_ops import *
|
7
|
+
from db2_hj3415.nfs._ops import *
|
8
|
+
from db2_hj3415.nfs.models import *
|
db2_hj3415/nfs/_c10346.py
CHANGED
@@ -4,9 +4,11 @@ from motor.motor_asyncio import AsyncIOMotorClient
|
|
4
4
|
import pprint
|
5
5
|
import pandas as pd
|
6
6
|
import json
|
7
|
-
from db2_hj3415.nfs import DB_NAME
|
7
|
+
from db2_hj3415.nfs import DB_NAME, C103
|
8
8
|
from db2_hj3415.common.db_ops import get_collection
|
9
9
|
from datetime import datetime, timezone
|
10
|
+
import numpy as np
|
11
|
+
|
10
12
|
|
11
13
|
from utils_hj3415 import setup_logger
|
12
14
|
|
@@ -59,30 +61,15 @@ async def _compare_and_log_diff(code: str, new_doc: dict, latest_doc: dict | Non
|
|
59
61
|
|
60
62
|
|
61
63
|
def _prepare_c10346_document(code: str, data: dict[str, pd.DataFrame]) -> dict:
|
62
|
-
"""
|
63
|
-
종목 코드와 여러 페이지의 DataFrame 데이터를 바탕으로 MongoDB에 저장할 문서(dict)를 생성합니다.
|
64
|
-
|
65
|
-
각 DataFrame은 null 값을 None으로 변환한 후, 레코드(행) 단위의 딕셔너리 리스트로 변환됩니다.
|
66
|
-
생성된 문서에는 '코드', '날짜', 그리고 각 페이지 이름을 키로 하는 데이터가 포함됩니다.
|
67
|
-
|
68
|
-
Args:
|
69
|
-
code (str): 종목 코드 (6자리 문자열).
|
70
|
-
data (dict[str, pd.DataFrame]): 페이지 이름을 키로 하고, 해당 페이지의 데이터를 담은 DataFrame을 값으로 가지는 딕셔너리.
|
71
|
-
|
72
|
-
Returns:
|
73
|
-
dict: MongoDB에 저장 가능한 형식의 문서. 예: {
|
74
|
-
"코드": "005930",
|
75
|
-
"날짜": <datetime>,
|
76
|
-
"재무상태표y": <DataFrame>,
|
77
|
-
"손익계산서y": <DataFrame>,
|
78
|
-
...
|
79
|
-
}
|
80
|
-
"""
|
81
64
|
now = datetime.now(timezone.utc)
|
82
65
|
document = {"코드": code, "날짜": now}
|
66
|
+
|
83
67
|
for page, df in data.items():
|
84
68
|
if isinstance(df, pd.DataFrame):
|
85
|
-
|
69
|
+
# NaN을 None으로 안전하게 변환
|
70
|
+
cleaned = df.replace({np.nan: None})
|
71
|
+
document[page] = cleaned.to_dict(orient="records")
|
72
|
+
|
86
73
|
return document
|
87
74
|
|
88
75
|
|
@@ -138,7 +125,7 @@ async def save_many(col: str, many_data: dict[str, dict[str, pd.DataFrame]], cli
|
|
138
125
|
return results
|
139
126
|
|
140
127
|
|
141
|
-
async def get_latest(col: str, code: str,
|
128
|
+
async def get_latest(col: str, code: str, client: AsyncIOMotorClient) -> C103 | None:
|
142
129
|
collection = get_collection(client, DB_NAME, col)
|
143
130
|
|
144
131
|
# 최신 날짜 기준으로 정렬하여 1건만 조회
|
@@ -147,14 +134,17 @@ async def get_latest(col: str, code: str, page: str, client: AsyncIOMotorClient)
|
|
147
134
|
sort=[("날짜", DESCENDING)]
|
148
135
|
)
|
149
136
|
|
150
|
-
if not latest_doc
|
151
|
-
print(f"문서
|
137
|
+
if not latest_doc:
|
138
|
+
print(f"문서 없음: {code}")
|
152
139
|
return None
|
153
140
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
141
|
+
latest_doc["_id"] = str(latest_doc["_id"])
|
142
|
+
|
143
|
+
try:
|
144
|
+
return C103(**latest_doc)
|
145
|
+
except Exception as e:
|
146
|
+
print(f"[{code}] C103 파싱 실패: {e}")
|
147
|
+
return None
|
158
148
|
|
159
149
|
|
160
150
|
async def has_doc_changed(col: str, code: str, client: AsyncIOMotorClient) -> bool:
|
db2_hj3415/nfs/c101.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
+
from typing import Literal
|
2
|
+
|
1
3
|
from pymongo import ASCENDING, UpdateOne, DESCENDING
|
2
4
|
from motor.motor_asyncio import AsyncIOMotorClient
|
3
5
|
from datetime import datetime, timezone
|
4
|
-
import pandas as pd
|
5
6
|
|
6
|
-
from db2_hj3415.nfs import DATE_FORMAT, DB_NAME
|
7
|
+
from db2_hj3415.nfs import DATE_FORMAT, DB_NAME, C101
|
7
8
|
from db2_hj3415.common.db_ops import get_collection
|
8
9
|
from utils_hj3415 import setup_logger
|
9
10
|
|
@@ -91,7 +92,7 @@ async def save_many(many_data: dict[str, dict | None], client: AsyncIOMotorClien
|
|
91
92
|
return {"inserted": inserted, "updated": updated}
|
92
93
|
|
93
94
|
|
94
|
-
async def get_latest(code: str, client: AsyncIOMotorClient) ->
|
95
|
+
async def get_latest(code: str, client: AsyncIOMotorClient) -> C101 | None:
|
95
96
|
collection = get_collection(client, DB_NAME, COL_NAME)
|
96
97
|
doc = await collection.find_one(
|
97
98
|
{"코드": code},
|
@@ -99,26 +100,45 @@ async def get_latest(code: str, client: AsyncIOMotorClient) -> dict | None:
|
|
99
100
|
)
|
100
101
|
|
101
102
|
if doc:
|
102
|
-
doc
|
103
|
-
|
103
|
+
doc["_id"] = str(doc["_id"])
|
104
|
+
mylogger.debug(doc)
|
105
|
+
return C101(**doc)
|
104
106
|
else:
|
105
107
|
print(f"데이터 없음: {code}")
|
106
108
|
return None
|
107
109
|
|
108
110
|
|
109
|
-
|
111
|
+
SortOrder = Literal["asc", "desc"]
|
112
|
+
|
113
|
+
async def get_all_data(code: str, client: AsyncIOMotorClient, sort: SortOrder = 'asc') -> list[C101]:
|
114
|
+
"""
|
115
|
+
지정한 종목 코드의 C101 도큐먼트 전체를 날짜 기준으로 정렬하여 반환합니다.
|
116
|
+
|
117
|
+
Args:
|
118
|
+
code (str): 조회할 종목 코드 (예: "005930").
|
119
|
+
client (AsyncIOMotorClient): 비동기 MongoDB 클라이언트 인스턴스.
|
120
|
+
sort (Literal["asc", "desc"], optional): 날짜 정렬 방식.
|
121
|
+
- "asc": 오름차순 (과거 → 최신)
|
122
|
+
- "desc": 내림차순 (최신 → 과거)
|
123
|
+
기본값은 "asc".
|
124
|
+
|
125
|
+
Returns:
|
126
|
+
list[C101]: 정렬된 C101 모델 리스트.
|
127
|
+
문서가 없을 경우 빈 리스트를 반환합니다.
|
128
|
+
"""
|
110
129
|
collection = get_collection(client, DB_NAME, COL_NAME)
|
111
|
-
|
130
|
+
sort_order = ASCENDING if sort == "asc" else DESCENDING
|
131
|
+
cursor = collection.find({"코드": code}).sort("날짜", sort_order)
|
112
132
|
docs = await cursor.to_list(length=None)
|
113
133
|
|
114
134
|
if not docs:
|
115
135
|
print(f"[{code}] 관련 문서 없음")
|
116
|
-
return
|
136
|
+
return []
|
117
137
|
|
118
|
-
|
138
|
+
result: list[C101] = []
|
119
139
|
for doc in docs:
|
120
|
-
doc
|
140
|
+
doc["_id"] = str(doc["_id"]) # ObjectId → str (C101에서 id: str)
|
141
|
+
result.append(C101(**doc))
|
121
142
|
|
122
|
-
|
123
|
-
return df
|
143
|
+
return result
|
124
144
|
|
db2_hj3415/nfs/models.py
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
from pydantic import BaseModel, Field, field_serializer, ConfigDict
|
2
|
+
from datetime import datetime
|
3
|
+
|
4
|
+
class C101(BaseModel):
|
5
|
+
id: str | None = Field(alias="_id")
|
6
|
+
날짜: datetime
|
7
|
+
코드: str
|
8
|
+
bps: int | None
|
9
|
+
eps: int | None
|
10
|
+
pbr: float | None
|
11
|
+
per: float | None
|
12
|
+
개요: str | None
|
13
|
+
거래대금: int | None
|
14
|
+
거래량: int | None
|
15
|
+
발행주식: int | None
|
16
|
+
배당수익률: float | None
|
17
|
+
베타52주: float | None
|
18
|
+
수익률: float | None
|
19
|
+
수익률1M: float | None
|
20
|
+
수익률1Y: float | None
|
21
|
+
수익률3M: float | None
|
22
|
+
수익률6M: float | None
|
23
|
+
시가총액: int | None
|
24
|
+
업종: str | None
|
25
|
+
업종per: float | None
|
26
|
+
외국인지분율: float | None
|
27
|
+
유동비율: float | None
|
28
|
+
전일대비: int | None
|
29
|
+
종목명: str | None
|
30
|
+
주가: int | None
|
31
|
+
최고52: int | None
|
32
|
+
최저52: int | None
|
33
|
+
|
34
|
+
@field_serializer("날짜")
|
35
|
+
def serialize_날짜(self, value: datetime) -> str:
|
36
|
+
return value.isoformat()
|
37
|
+
|
38
|
+
model_config = ConfigDict(
|
39
|
+
populate_by_name=True,
|
40
|
+
str_strip_whitespace=True,
|
41
|
+
)
|
42
|
+
|
43
|
+
class C103재무항목y(BaseModel):
|
44
|
+
항목: str
|
45
|
+
전년대비: float | None
|
46
|
+
전년대비_1: float | None = Field(default=None, alias="전년대비 1")
|
47
|
+
|
48
|
+
model_config = ConfigDict(
|
49
|
+
populate_by_name=True,
|
50
|
+
extra="allow"
|
51
|
+
)
|
52
|
+
|
53
|
+
class C103재무항목q(BaseModel):
|
54
|
+
항목: str
|
55
|
+
전분기대비: float | None
|
56
|
+
|
57
|
+
model_config = ConfigDict(extra="allow") # 나머지 키들 허용
|
58
|
+
|
59
|
+
class C103(BaseModel):
|
60
|
+
id: str = Field(alias="_id")
|
61
|
+
코드: str
|
62
|
+
날짜: datetime
|
63
|
+
손익계산서q: list[C103재무항목q]
|
64
|
+
손익계산서y: list[C103재무항목y]
|
65
|
+
재무상태표q: list[C103재무항목q]
|
66
|
+
재무상태표y: list[C103재무항목y]
|
67
|
+
현금흐름표q: list[C103재무항목q]
|
68
|
+
현금흐름표y: list[C103재무항목y]
|
69
|
+
|
70
|
+
@field_serializer("날짜")
|
71
|
+
def serialize_date(self, v: datetime) -> str:
|
72
|
+
return v.isoformat()
|
73
|
+
|
74
|
+
model_config = ConfigDict(populate_by_name=True)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
from db2_hj3415.common import connection
|
2
|
+
import json
|
3
|
+
|
4
|
+
async def get_or_set_cache(key, ttl, fetch_func, force_refresh=False):
|
5
|
+
redis = await connection.get_redis()
|
6
|
+
if not force_refresh:
|
7
|
+
cached = await redis.get(key)
|
8
|
+
if cached:
|
9
|
+
return json.loads(cached)
|
10
|
+
|
11
|
+
result = await fetch_func()
|
12
|
+
if result:
|
13
|
+
await redis.setex(key, ttl, json.dumps(result))
|
14
|
+
return result
|
15
|
+
|
16
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: db2_hj3415
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.3
|
4
4
|
Summary: Gathering the stock data by playwright
|
5
5
|
Author-email: Hyungjin Kim <hj3415@gmail.com>
|
6
6
|
Description-Content-Type: text/markdown
|
@@ -10,6 +10,8 @@ Requires-Dist: pandas
|
|
10
10
|
Requires-Dist: pandas-stubs
|
11
11
|
Requires-Dist: deepdiff
|
12
12
|
Requires-Dist: utils_hj3415>=3.2.3
|
13
|
+
Requires-Dist: redis
|
14
|
+
Requires-Dist: pydantic
|
13
15
|
Project-URL: Home, https://www.hyungjin.kr
|
14
16
|
|
15
17
|
### db2-hj3415
|
@@ -1,11 +1,12 @@
|
|
1
1
|
db2_hj3415/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
db2_hj3415/redis_manager.py,sha256=yc8dxEwfhz9k4qwWRnUyhIfuPBHdqg35sL2w1lhG0_4,411
|
2
3
|
db2_hj3415/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
4
|
db2_hj3415/cli/db.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
5
|
db2_hj3415/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
db2_hj3415/common/connection.py,sha256
|
6
|
+
db2_hj3415/common/connection.py,sha256=QpjDtDHy6By0MCj81z5upJkWeuIgqeIsbXsihHcpKX4,774
|
6
7
|
db2_hj3415/common/db_ops.py,sha256=CajXob5FofIzsuyFvtlOEo1UkdYMfsLUwKDmbb29Kas,212
|
7
|
-
db2_hj3415/common/utils.py,sha256=
|
8
|
-
db2_hj3415/mi/__init__.py,sha256=
|
8
|
+
db2_hj3415/common/utils.py,sha256=uL5EqR9ZPkeoGex983Mhwl7CckIfZtGGlQA9mIsl8uY,235
|
9
|
+
db2_hj3415/mi/__init__.py,sha256=4XIwdkLfdUCiq2zLqCnR5ypuI45nzX02qez80xWQ70A,159
|
9
10
|
db2_hj3415/mi/_ops.py,sha256=Xm2ZpnKxAymIieut8JyK3epHED8uMlhO77ScACfoB7g,3988
|
10
11
|
db2_hj3415/mi/aud.py,sha256=w4w4EC0hn6kKHjNkeIUL5ZsvHH4IzRGBpTKRptHapVY,765
|
11
12
|
db2_hj3415/mi/chf.py,sha256=Sx8HCc0O0sfjiYmlfwozts0hm-bszkU0CgKe3-2fXbc,768
|
@@ -18,17 +19,17 @@ db2_hj3415/mi/sp500.py,sha256=2OFCo6SxXgLijOGLQhV9Ygh0OR10IviGXI0EjKcwfOI,800
|
|
18
19
|
db2_hj3415/mi/usdidx.py,sha256=cT6w-Y-Ysv0KI9yeFqDQYCdT0KPElwn8IZ0JF5oZ_MA,550
|
19
20
|
db2_hj3415/mi/usdkrw.py,sha256=QjcHqFSpSgyzI5lzRfYycNUzolzM-ikOvPDFy4FPadM,876
|
20
21
|
db2_hj3415/mi/wti.py,sha256=wMoS5yHGOlUS2b2P7Eok5U_59eehYXQGfqWa56J_dkk,768
|
21
|
-
db2_hj3415/nfs/__init__.py,sha256=
|
22
|
-
db2_hj3415/nfs/_c10346.py,sha256=
|
22
|
+
db2_hj3415/nfs/__init__.py,sha256=w5QUgr_qWQNHRVcLrT8G8Rx6pNry2PnHfyezBDI5D2A,229
|
23
|
+
db2_hj3415/nfs/_c10346.py,sha256=ni2Nrqxwuuy4qYTtSpodFQcHPeGTHnR5-kQtsw1x1UU,6699
|
23
24
|
db2_hj3415/nfs/_ops.py,sha256=lqrBBhEFcPupB2dQBXZU-Gftvd9dYTlmgJGTb5LdX-o,1193
|
24
|
-
db2_hj3415/nfs/c101.py,sha256=
|
25
|
+
db2_hj3415/nfs/c101.py,sha256=ORCNXr3pq43zn4naSnyu1erDxFmDc2HLpXO19gv0KNo,4853
|
25
26
|
db2_hj3415/nfs/c103.py,sha256=n5xwq8Ire5nBslFrZuwfNHTJZDRZh-_UnbU1KhHFEAM,1479
|
26
27
|
db2_hj3415/nfs/c104.py,sha256=QpYOct3JCoX6scyM98YNO_9J_RB3T_odkXa5186UvfI,1479
|
27
28
|
db2_hj3415/nfs/c106.py,sha256=-kJ90dzUzyqamjBPxhihOjl9rw0Swfb0bVoThApCBfI,1479
|
28
29
|
db2_hj3415/nfs/c108.py,sha256=vmm_vaxEixDZeOWMsfgLzAj4LvlcyCqe_V4E9WnIVHo,2604
|
29
30
|
db2_hj3415/nfs/dart.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
|
-
db2_hj3415/
|
31
|
-
db2_hj3415-0.1.
|
32
|
-
db2_hj3415-0.1.
|
33
|
-
db2_hj3415-0.1.
|
34
|
-
db2_hj3415-0.1.
|
31
|
+
db2_hj3415/nfs/models.py,sha256=fJ5ZizdAXTUOdovHVpcZ87PELjmXKFnBeddkiM0u5IE,2053
|
32
|
+
db2_hj3415-0.1.3.dist-info/entry_points.txt,sha256=JAGFsxKtvbNgaKCSKo-QRYwCyToabWmdRW0MZWtg9kg,45
|
33
|
+
db2_hj3415-0.1.3.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
34
|
+
db2_hj3415-0.1.3.dist-info/METADATA,sha256=Jhv1_XJPNrsXny3QBc-q2O_QnNCuSr_hsuVUzrbjsyc,709
|
35
|
+
db2_hj3415-0.1.3.dist-info/RECORD,,
|
db2_hj3415/redis/__init__.py
DELETED
File without changes
|
File without changes
|
File without changes
|