linkmerce 0.2.1__tar.gz → 0.2.2__tar.gz
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.
- {linkmerce-0.2.1 → linkmerce-0.2.2}/PKG-INFO +2 -2
- {linkmerce-0.2.1 → linkmerce-0.2.2}/pyproject.toml +2 -2
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/base.py +2 -36
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/naver/api/__init__.py +1 -1
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/naver/api/search.py +1 -1
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/searchad/manage/__init__.py +2 -2
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/searchad/manage/exposure.py +1 -1
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/smartstore/hcenter/catalog.py +1 -1
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/smartstore/hcenter/sales.py +1 -1
- linkmerce-0.2.1/src/linkmerce/types.py → linkmerce-0.2.2/src/linkmerce/common.py +16 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/base.py +1 -1
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/naver/api/search.py +2 -2
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/searchad/manage/exposure.py +3 -3
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/smartstore/hcenter/catalog.py +2 -2
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/smartstore/hcenter/sales.py +3 -3
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/duckdb.py +73 -45
- linkmerce-0.2.2/src/linkmerce/utils/pyarrow.py +1136 -0
- linkmerce-0.2.1/src/linkmerce/exceptions.py +0 -17
- linkmerce-0.2.1/src/linkmerce/extensions/bigquery.py +0 -195
- linkmerce-0.2.1/src/linkmerce/extensions/gspread.py +0 -262
- linkmerce-0.2.1/src/linkmerce/extensions/pandas.py +0 -114
- {linkmerce-0.2.1 → linkmerce-0.2.2}/README.md +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/auth/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/auth/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/naver/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/naver/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/naver/api/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/searchad/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/smartstore/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/smartstore/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/smartstore/hcenter/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/collect/tasks.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/naver/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/naver/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/naver/api/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/naver/api/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/searchad/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/searchad/manage/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/smartstore/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/smartstore/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/smartstore/hcenter/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/parse/smartstore/hcenter/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/.DS_Store +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/__init__.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/cast.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/datetime.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/graphql.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/headers.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/jinja.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/map.py +0 -0
- {linkmerce-0.2.1 → linkmerce-0.2.2}/src/linkmerce/utils/tqdm.py +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: linkmerce
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: E-commerce API integration management
|
|
5
5
|
Requires-Dist: aiohttp>=3.12.14
|
|
6
|
-
Requires-Dist: asyncio>=3.4.3
|
|
7
6
|
Requires-Dist: bs4>=0.0.2
|
|
8
7
|
Requires-Dist: duckdb>=1.3.2
|
|
9
8
|
Requires-Dist: jinja2>=3.1.6
|
|
9
|
+
Requires-Dist: pyarrow>=21.0.0
|
|
10
10
|
Requires-Dist: pytz>=2025.2
|
|
11
11
|
Requires-Dist: requests>=2.32.4
|
|
12
12
|
Requires-Dist: tqdm>=4.67.1
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "linkmerce"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "E-commerce API integration management"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
7
|
dependencies = [
|
|
8
8
|
"aiohttp>=3.12.14",
|
|
9
|
-
"asyncio>=3.4.3",
|
|
10
9
|
"bs4>=0.0.2",
|
|
11
10
|
"duckdb>=1.3.2",
|
|
12
11
|
"jinja2>=3.1.6",
|
|
12
|
+
"pyarrow>=21.0.0",
|
|
13
13
|
"pytz>=2025.2",
|
|
14
14
|
"requests>=2.32.4",
|
|
15
15
|
"tqdm>=4.67.1",
|
|
@@ -9,8 +9,8 @@ if TYPE_CHECKING:
|
|
|
9
9
|
from typing import Any, Coroutine, Hashable, IO, Literal, TypeVar
|
|
10
10
|
from linkmerce.collect.tasks import RequestLoop, RequestEach
|
|
11
11
|
from linkmerce.collect.tasks import PaginateAll, RequestEachPages
|
|
12
|
-
from linkmerce.
|
|
13
|
-
from linkmerce.
|
|
12
|
+
from linkmerce.common import JsonObject, JsonSerialize, ResponseComponent
|
|
13
|
+
from linkmerce.common import TaskOption, TaskOptions
|
|
14
14
|
_KT = TypeVar("_KT", Hashable)
|
|
15
15
|
_VT = TypeVar("_VT", Any)
|
|
16
16
|
|
|
@@ -221,23 +221,6 @@ class RequestSessionClient(BaseSessionClient):
|
|
|
221
221
|
response = self.request_text(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
|
|
222
222
|
return BeautifulSoup(response, features)
|
|
223
223
|
|
|
224
|
-
def request_table(
|
|
225
|
-
self,
|
|
226
|
-
method: str,
|
|
227
|
-
url: str,
|
|
228
|
-
params: dict | list[tuple] | bytes | None = None,
|
|
229
|
-
data: dict | list[tuple] | bytes | IO | None = None,
|
|
230
|
-
json: JsonSerialize | None = None,
|
|
231
|
-
headers: dict[str,str] = None,
|
|
232
|
-
cookies: dict | RequestsCookieJar = None,
|
|
233
|
-
content_type: Literal["excel", "csv", "html", "xml"] | Sequence = "xlsx",
|
|
234
|
-
table_options: dict = dict(),
|
|
235
|
-
**kwargs
|
|
236
|
-
):
|
|
237
|
-
from linkmerce.extensions.pandas import read_table
|
|
238
|
-
response = self.request_content(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
|
|
239
|
-
return read_table(response, table_format=content_type, **table_options)
|
|
240
|
-
|
|
241
224
|
def with_session(func):
|
|
242
225
|
@functools.wraps(func)
|
|
243
226
|
def wrapper(self: RequestSessionClient, *args, init_session: bool = False, **kwargs):
|
|
@@ -365,23 +348,6 @@ class AiohttpSessionClient(BaseSessionClient):
|
|
|
365
348
|
response = await self.request_async_text(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
|
|
366
349
|
return BeautifulSoup(response, features)
|
|
367
350
|
|
|
368
|
-
async def request_async_table(
|
|
369
|
-
self,
|
|
370
|
-
method: str,
|
|
371
|
-
url: str,
|
|
372
|
-
params: dict | list[tuple] | bytes | None = None,
|
|
373
|
-
data: dict | list[tuple] | bytes | IO | None = None,
|
|
374
|
-
json: JsonSerialize | None = None,
|
|
375
|
-
headers: dict[str,str] = None,
|
|
376
|
-
cookies: dict | LooseCookies = None,
|
|
377
|
-
content_type: Literal["excel", "csv", "html", "xml"] | Sequence = "xlsx",
|
|
378
|
-
table_options: dict = dict(),
|
|
379
|
-
**kwargs
|
|
380
|
-
):
|
|
381
|
-
from linkmerce.extensions.pandas import read_table
|
|
382
|
-
response = await self.request_async_content(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
|
|
383
|
-
return read_table(response, table_format=content_type, **table_options)
|
|
384
|
-
|
|
385
351
|
def async_with_session(func):
|
|
386
352
|
@functools.wraps(func)
|
|
387
353
|
async def wrapper(self: AiohttpSessionClient, *args, init_session: bool = False, **kwargs):
|
|
@@ -6,7 +6,7 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
if TYPE_CHECKING:
|
|
8
8
|
from typing import Any, Callable, Hashable, IO, Literal, TypeVar
|
|
9
|
-
from linkmerce.
|
|
9
|
+
from linkmerce.common import JsonSerialize
|
|
10
10
|
_KT = TypeVar("_KT", Hashable)
|
|
11
11
|
_VT = TypeVar("_VT", Any)
|
|
12
12
|
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from typing import Any, Callable, Hashable, IO, Literal, TypeVar
|
|
10
|
-
from linkmerce.
|
|
10
|
+
from linkmerce.common import JsonSerialize
|
|
11
11
|
_KT = TypeVar("_KT", Hashable)
|
|
12
12
|
_VT = TypeVar("_VT", Any)
|
|
13
13
|
|
|
@@ -62,7 +62,7 @@ class SearchAdManager(Collector):
|
|
|
62
62
|
headers = super().get_request_headers(referer=redirect_url, origin=self.origin)
|
|
63
63
|
response = self.request_text("GET", url, headers=headers)
|
|
64
64
|
if response.strip() != "true":
|
|
65
|
-
from linkmerce.
|
|
65
|
+
from linkmerce.common import AuthenticationError
|
|
66
66
|
raise AuthenticationError("Authentication failed: cookies are invalid.")
|
|
67
67
|
|
|
68
68
|
def authorize(self):
|
|
@@ -6,7 +6,7 @@ from typing import Iterable, TYPE_CHECKING
|
|
|
6
6
|
if TYPE_CHECKING:
|
|
7
7
|
from typing import Callable, Coroutine, Literal, Sequence
|
|
8
8
|
from linkmerce.collect.tasks import RequestEachPages
|
|
9
|
-
from linkmerce.
|
|
9
|
+
from linkmerce.common import JsonObject, TaskOptions
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class _CatalogCollector(PartnerCenter):
|
|
@@ -12,3 +12,19 @@ ResponseComponent = Union[int, bytes, str, JsonObject, dict[str,str]]
|
|
|
12
12
|
|
|
13
13
|
TaskOption = Dict[_KT,_VT]
|
|
14
14
|
TaskOptions = Dict[_KT,TaskOption]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AuthenticationError(RuntimeError):
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ParseError(ValueError):
|
|
22
|
+
...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RequestError(RuntimeError):
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class UnauthorizedError(RuntimeError):
|
|
30
|
+
...
|
|
@@ -21,7 +21,7 @@ class Parser(metaclass=ABCMeta):
|
|
|
21
21
|
raise NotImplementedError("The 'parse' method must be implemented.")
|
|
22
22
|
|
|
23
23
|
def raise_parse_error(self, msg: str):
|
|
24
|
-
from linkmerce.
|
|
24
|
+
from linkmerce.common import ParseError
|
|
25
25
|
raise ParseError(msg)
|
|
26
26
|
|
|
27
27
|
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from typing import Literal
|
|
10
|
-
from linkmerce.
|
|
10
|
+
from linkmerce.common import JsonObject
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class _SearchParser(QueryParser):
|
|
@@ -27,7 +27,7 @@ class _SearchParser(QueryParser):
|
|
|
27
27
|
return wrapper
|
|
28
28
|
|
|
29
29
|
def raise_request_error(self, response: dict):
|
|
30
|
-
from linkmerce.
|
|
30
|
+
from linkmerce.common import RequestError
|
|
31
31
|
raise RequestError(response.get("errorMessage") or str())
|
|
32
32
|
|
|
33
33
|
@check_errors
|
|
@@ -6,7 +6,7 @@ import functools
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from linkmerce.
|
|
9
|
+
from linkmerce.common import JsonObject
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ExposureDiagnosis(QueryParser):
|
|
@@ -23,7 +23,7 @@ class ExposureDiagnosis(QueryParser):
|
|
|
23
23
|
return wrapper
|
|
24
24
|
|
|
25
25
|
def raise_request_error(self, response: dict):
|
|
26
|
-
from linkmerce.
|
|
26
|
+
from linkmerce.common import RequestError, UnauthorizedError
|
|
27
27
|
msg = response.get("title") or response.get("message") or str()
|
|
28
28
|
if (msg == "Forbidden") or ("권한이 없습니다." in msg) or ("인증이 만료됐습니다." in msg):
|
|
29
29
|
raise UnauthorizedError(msg)
|
|
@@ -47,7 +47,7 @@ class ExposureDiagnosis(QueryParser):
|
|
|
47
47
|
NULLIF(fmpBrand, '') AS mallName,
|
|
48
48
|
NULLIF(fmpMaker, '') AS makerName,
|
|
49
49
|
imageUrl,
|
|
50
|
-
CAST(COALESCE(lowPrice, mobileLowPrice, NULL) AS INT64) AS salesPrice,
|
|
50
|
+
-- CAST(COALESCE(lowPrice, mobileLowPrice, NULL) AS INT64) AS salesPrice,
|
|
51
51
|
{{ created_at }} AS createdAt
|
|
52
52
|
FROM {{ table }}
|
|
53
53
|
{{ where }}
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from typing import Literal
|
|
10
|
-
from linkmerce.
|
|
10
|
+
from linkmerce.common import JsonObject
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class _CatalogParser(QueryParser):
|
|
@@ -27,7 +27,7 @@ class _CatalogParser(QueryParser):
|
|
|
27
27
|
|
|
28
28
|
def raise_request_error(self, response: dict):
|
|
29
29
|
from linkmerce.utils.map import hier_get
|
|
30
|
-
from linkmerce.
|
|
30
|
+
from linkmerce.common import RequestError
|
|
31
31
|
msg = hier_get(response, ["errors",0,"message"]) or "null"
|
|
32
32
|
raise RequestError(f"An error occurred during the request: {msg}")
|
|
33
33
|
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
9
|
from typing import Literal
|
|
10
|
-
from linkmerce.
|
|
10
|
+
from linkmerce.common import JsonObject
|
|
11
11
|
import datetime as dt
|
|
12
12
|
|
|
13
13
|
|
|
@@ -30,10 +30,10 @@ class _SalesParser(QueryParser):
|
|
|
30
30
|
from linkmerce.utils.map import hier_get
|
|
31
31
|
msg = hier_get(response, ["error","error"]) or "null"
|
|
32
32
|
if msg == "Unauthorized":
|
|
33
|
-
from linkmerce.
|
|
33
|
+
from linkmerce.common import UnauthorizedError
|
|
34
34
|
raise UnauthorizedError("Unauthorized request")
|
|
35
35
|
else:
|
|
36
|
-
from linkmerce.
|
|
36
|
+
from linkmerce.common import RequestError
|
|
37
37
|
raise RequestError(f"An error occurred during the request: {msg}")
|
|
38
38
|
|
|
39
39
|
@check_errors
|
|
@@ -6,8 +6,9 @@ import functools
|
|
|
6
6
|
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from typing import Any,
|
|
9
|
+
from typing import Any, Literal, Sequence
|
|
10
10
|
from duckdb import DuckDBPyConnection
|
|
11
|
+
from linkmerce.utils.pyarrow import Table
|
|
11
12
|
|
|
12
13
|
DEFAULT_TEMP_TABLE = "temp_table"
|
|
13
14
|
|
|
@@ -79,6 +80,17 @@ def select_to_json(
|
|
|
79
80
|
return [dict(zip(columns, row)) for row in relation.fetchall()]
|
|
80
81
|
|
|
81
82
|
|
|
83
|
+
def select_to_arrow(
|
|
84
|
+
query: str,
|
|
85
|
+
table: tuple[str,Table],
|
|
86
|
+
params: dict | None = None,
|
|
87
|
+
conn: DuckDBPyConnection | None = None,
|
|
88
|
+
) -> Table:
|
|
89
|
+
with duckdb.connect() as conn:
|
|
90
|
+
conn.register(*table)
|
|
91
|
+
return conn.execute(query, parameters=params).arrow()
|
|
92
|
+
|
|
93
|
+
|
|
82
94
|
###################################################################
|
|
83
95
|
############################# Datetime ############################
|
|
84
96
|
###################################################################
|
|
@@ -200,52 +212,68 @@ def combine_first(
|
|
|
200
212
|
########################### Partition By ##########################
|
|
201
213
|
###################################################################
|
|
202
214
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
215
|
+
class Partition:
|
|
216
|
+
def __init__(
|
|
217
|
+
self,
|
|
218
|
+
data: list[dict],
|
|
219
|
+
field: str,
|
|
220
|
+
type: str | None = None,
|
|
221
|
+
condition: str | None = None,
|
|
222
|
+
sort: bool = True,
|
|
223
|
+
temp_table: str = DEFAULT_TEMP_TABLE,
|
|
224
|
+
):
|
|
225
|
+
self.conn = duckdb.connect()
|
|
226
|
+
self.table = temp_table
|
|
227
|
+
try:
|
|
228
|
+
self.set_data(data)
|
|
229
|
+
self.set_field(field, type)
|
|
230
|
+
self.set_partitions(condition, sort)
|
|
231
|
+
except:
|
|
232
|
+
...
|
|
233
|
+
|
|
234
|
+
def set_data(self, data: list[dict]):
|
|
235
|
+
create_table(self.conn, self.table, data, option="ignore", temp=True)
|
|
236
|
+
|
|
237
|
+
def set_field(self, field: str, type: str | None = None):
|
|
238
|
+
if field not in get_columns(self.conn, self.table):
|
|
239
|
+
field = "_PARTITIONFIELD"
|
|
240
|
+
if not type:
|
|
241
|
+
type = self.conn.execute(f"SELECT {field} FROM {self.table} LIMIT 1").description[0][TYPE]
|
|
242
|
+
self.conn.execute(f"ALTER TABLE {self.table} ADD COLUMN {field} {type};")
|
|
243
|
+
self.conn.execute(f"UPDATE {self.table} SET {field} = {field};")
|
|
244
|
+
self.field = field
|
|
245
|
+
|
|
246
|
+
def set_partitions(self, condition: str | None = None, sort: bool = True):
|
|
247
|
+
query = f"SELECT DISTINCT {self.field} FROM {self.table} {_where(condition, self.field)};"
|
|
248
|
+
if sort:
|
|
249
|
+
self.partitions = sorted(map(lambda x: x[0], self.conn.execute(query).fetchall()))
|
|
250
|
+
else:
|
|
251
|
+
self.partitions = [row[0] for row in self.conn.execute(query).fetchall()]
|
|
252
|
+
|
|
253
|
+
def __iter__(self) -> Partition:
|
|
254
|
+
self.index = 0
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
def __next__(self) -> list[dict]:
|
|
258
|
+
if self.index < len(self):
|
|
259
|
+
exclude = "EXCLUDE (_PARTITIONFIELD)" if self.field == "_PARTITIONFIELD" else str()
|
|
260
|
+
query = f"SELECT * {exclude} FROM temp_table WHERE {self.field} = {_quote(self.partitions[self.index])};"
|
|
261
|
+
self.index += 1
|
|
262
|
+
return select_to_json(query, conn=self.conn)
|
|
263
|
+
else:
|
|
264
|
+
raise StopIteration
|
|
235
265
|
|
|
266
|
+
def __len__(self) -> int:
|
|
267
|
+
return len(self.partitions)
|
|
236
268
|
|
|
237
|
-
def
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
if sort:
|
|
246
|
-
return sorted(map(lambda x: x[0], conn.execute(query).fetchall()))
|
|
247
|
-
else:
|
|
248
|
-
return [row[0] for row in conn.execute(query).fetchall()]
|
|
269
|
+
def __exit__(self):
|
|
270
|
+
self.close()
|
|
271
|
+
|
|
272
|
+
def close(self):
|
|
273
|
+
try:
|
|
274
|
+
self.conn.close()
|
|
275
|
+
except:
|
|
276
|
+
pass
|
|
249
277
|
|
|
250
278
|
|
|
251
279
|
def _where(condition: str | None = None, field: str | None = None, **kwargs) -> str:
|