linkmerce 0.2.2__tar.gz → 0.3.0__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.
Files changed (79) hide show
  1. {linkmerce-0.2.2 → linkmerce-0.3.0}/PKG-INFO +2 -3
  2. {linkmerce-0.2.2 → linkmerce-0.3.0}/pyproject.toml +2 -3
  3. linkmerce-0.3.0/src/linkmerce/.DS_Store +0 -0
  4. {linkmerce-0.2.2/src/linkmerce/auth → linkmerce-0.3.0/src/linkmerce/api}/__init__.py +0 -0
  5. {linkmerce-0.2.2/src/linkmerce/collect → linkmerce-0.3.0/src/linkmerce/api}/naver/__init__.py +0 -0
  6. linkmerce-0.3.0/src/linkmerce/api/naver/openapi.py +167 -0
  7. {linkmerce-0.2.2/src/linkmerce/collect → linkmerce-0.3.0/src/linkmerce/api}/searchad/__init__.py +0 -0
  8. linkmerce-0.3.0/src/linkmerce/api/searchad/manage.py +50 -0
  9. {linkmerce-0.2.2/src/linkmerce/collect → linkmerce-0.3.0/src/linkmerce/api}/smartstore/__init__.py +0 -0
  10. linkmerce-0.3.0/src/linkmerce/api/smartstore/brand.py +154 -0
  11. linkmerce-0.3.0/src/linkmerce/common/.DS_Store +0 -0
  12. linkmerce-0.3.0/src/linkmerce/common/api.py +156 -0
  13. linkmerce-0.3.0/src/linkmerce/common/exceptions.py +17 -0
  14. linkmerce-0.3.0/src/linkmerce/common/extract.py +439 -0
  15. linkmerce-0.3.0/src/linkmerce/common/load.py +195 -0
  16. linkmerce-0.3.0/src/linkmerce/common/models.py +83 -0
  17. {linkmerce-0.2.2/src/linkmerce/collect → linkmerce-0.3.0/src/linkmerce/common}/tasks.py +154 -120
  18. linkmerce-0.3.0/src/linkmerce/common/transform.py +399 -0
  19. {linkmerce-0.2.2/src/linkmerce/collect → linkmerce-0.3.0/src/linkmerce/core}/.DS_Store +0 -0
  20. {linkmerce-0.2.2/src/linkmerce/collect/smartstore → linkmerce-0.3.0/src/linkmerce/core/naver}/.DS_Store +0 -0
  21. {linkmerce-0.2.2/src/linkmerce/auth → linkmerce-0.3.0/src/linkmerce/core/naver/openapi}/.DS_Store +0 -0
  22. linkmerce-0.3.0/src/linkmerce/core/naver/openapi/__init__.py +1 -0
  23. linkmerce-0.3.0/src/linkmerce/core/naver/openapi/common.py +34 -0
  24. linkmerce-0.2.2/src/linkmerce/collect/naver/api/search.py → linkmerce-0.3.0/src/linkmerce/core/naver/openapi/search/extract.py +45 -57
  25. linkmerce-0.3.0/src/linkmerce/core/naver/openapi/search/models.sql +266 -0
  26. linkmerce-0.3.0/src/linkmerce/core/naver/openapi/search/transform.py +89 -0
  27. {linkmerce-0.2.2/src/linkmerce/collect/naver → linkmerce-0.3.0/src/linkmerce/core/searchad}/.DS_Store +0 -0
  28. {linkmerce-0.2.2/src/linkmerce/collect/naver/api → linkmerce-0.3.0/src/linkmerce/core/searchad/manage}/.DS_Store +0 -0
  29. linkmerce-0.3.0/src/linkmerce/core/searchad/manage/__init__.py +1 -0
  30. linkmerce-0.2.2/src/linkmerce/collect/searchad/manage/__init__.py → linkmerce-0.3.0/src/linkmerce/core/searchad/manage/common.py +24 -35
  31. {linkmerce-0.2.2/src/linkmerce/parse/smartstore/hcenter → linkmerce-0.3.0/src/linkmerce/core/searchad/manage/exposure}/__init__.py +0 -0
  32. linkmerce-0.2.2/src/linkmerce/collect/searchad/manage/exposure.py → linkmerce-0.3.0/src/linkmerce/core/searchad/manage/exposure/extract.py +15 -20
  33. linkmerce-0.3.0/src/linkmerce/core/searchad/manage/exposure/models.sql +108 -0
  34. linkmerce-0.3.0/src/linkmerce/core/searchad/manage/exposure/transform.py +59 -0
  35. linkmerce-0.3.0/src/linkmerce/core/smartstore/.DS_Store +0 -0
  36. {linkmerce-0.2.2/src/linkmerce/parse/smartstore → linkmerce-0.3.0/src/linkmerce/core/smartstore/brand}/.DS_Store +0 -0
  37. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/__init__.py +1 -0
  38. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/catalog/__init__.py +0 -0
  39. linkmerce-0.2.2/src/linkmerce/collect/smartstore/hcenter/catalog.py → linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/catalog/extract.py +113 -92
  40. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/catalog/models.sql +176 -0
  41. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/catalog/transform.py +64 -0
  42. linkmerce-0.2.2/src/linkmerce/collect/smartstore/hcenter/__init__.py → linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/common.py +2 -2
  43. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/sales/__init__.py +0 -0
  44. linkmerce-0.2.2/src/linkmerce/collect/smartstore/hcenter/sales.py → linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/sales/extract.py +19 -45
  45. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/sales/models.sql +164 -0
  46. linkmerce-0.3.0/src/linkmerce/core/smartstore/brand/sales/transform.py +91 -0
  47. linkmerce-0.3.0/src/linkmerce/utils/__init__.py +0 -0
  48. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/duckdb.py +74 -93
  49. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/headers.py +1 -1
  50. linkmerce-0.2.2/src/linkmerce/collect/__init__.py +0 -1
  51. linkmerce-0.2.2/src/linkmerce/collect/base.py +0 -540
  52. linkmerce-0.2.2/src/linkmerce/collect/naver/api/__init__.py +0 -48
  53. linkmerce-0.2.2/src/linkmerce/common.py +0 -30
  54. linkmerce-0.2.2/src/linkmerce/parse/.DS_Store +0 -0
  55. linkmerce-0.2.2/src/linkmerce/parse/__init__.py +0 -1
  56. linkmerce-0.2.2/src/linkmerce/parse/base.py +0 -134
  57. linkmerce-0.2.2/src/linkmerce/parse/naver/.DS_Store +0 -0
  58. linkmerce-0.2.2/src/linkmerce/parse/naver/api/.DS_Store +0 -0
  59. linkmerce-0.2.2/src/linkmerce/parse/naver/api/search.py +0 -215
  60. linkmerce-0.2.2/src/linkmerce/parse/searchad/manage/exposure.py +0 -77
  61. linkmerce-0.2.2/src/linkmerce/parse/smartstore/hcenter/.DS_Store +0 -0
  62. linkmerce-0.2.2/src/linkmerce/parse/smartstore/hcenter/catalog.py +0 -153
  63. linkmerce-0.2.2/src/linkmerce/parse/smartstore/hcenter/sales.py +0 -172
  64. linkmerce-0.2.2/src/linkmerce/utils/pyarrow.py +0 -1136
  65. {linkmerce-0.2.2 → linkmerce-0.3.0}/README.md +0 -0
  66. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/__init__.py +0 -0
  67. {linkmerce-0.2.2/src/linkmerce/parse/naver → linkmerce-0.3.0/src/linkmerce/common}/__init__.py +0 -0
  68. {linkmerce-0.2.2/src/linkmerce/parse/naver/api → linkmerce-0.3.0/src/linkmerce/core}/__init__.py +0 -0
  69. {linkmerce-0.2.2/src/linkmerce/parse/searchad → linkmerce-0.3.0/src/linkmerce/core/naver}/__init__.py +0 -0
  70. {linkmerce-0.2.2/src/linkmerce/parse/searchad/manage → linkmerce-0.3.0/src/linkmerce/core/naver/openapi/search}/__init__.py +0 -0
  71. {linkmerce-0.2.2/src/linkmerce/parse/smartstore → linkmerce-0.3.0/src/linkmerce/core/searchad}/__init__.py +0 -0
  72. {linkmerce-0.2.2/src/linkmerce/utils → linkmerce-0.3.0/src/linkmerce/core/smartstore}/__init__.py +0 -0
  73. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/.DS_Store +0 -0
  74. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/cast.py +0 -0
  75. /linkmerce-0.2.2/src/linkmerce/utils/datetime.py → /linkmerce-0.3.0/src/linkmerce/utils/date.py +0 -0
  76. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/graphql.py +0 -0
  77. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/jinja.py +0 -0
  78. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/map.py +0 -0
  79. {linkmerce-0.2.2 → linkmerce-0.3.0}/src/linkmerce/utils/tqdm.py +0 -0
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: linkmerce
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: E-commerce API integration management
5
- Requires-Dist: aiohttp>=3.12.14
5
+ Requires-Dist: aiohttp>=3.12.15
6
6
  Requires-Dist: bs4>=0.0.2
7
7
  Requires-Dist: duckdb>=1.3.2
8
8
  Requires-Dist: jinja2>=3.1.6
9
- Requires-Dist: pyarrow>=21.0.0
10
9
  Requires-Dist: pytz>=2025.2
11
10
  Requires-Dist: requests>=2.32.4
12
11
  Requires-Dist: tqdm>=4.67.1
@@ -1,15 +1,14 @@
1
1
  [project]
2
2
  name = "linkmerce"
3
- version = "0.2.2"
3
+ version = "0.3.0"
4
4
  description = "E-commerce API integration management"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
7
7
  dependencies = [
8
- "aiohttp>=3.12.14",
8
+ "aiohttp>=3.12.15",
9
9
  "bs4>=0.0.2",
10
10
  "duckdb>=1.3.2",
11
11
  "jinja2>=3.1.6",
12
- "pyarrow>=21.0.0",
13
12
  "pytz>=2025.2",
14
13
  "requests>=2.32.4",
15
14
  "tqdm>=4.67.1",
Binary file
@@ -0,0 +1,167 @@
1
+ from __future__ import annotations
2
+
3
+ from linkmerce.common.api import run_with_duckdb
4
+ from linkmerce.common.api import get_table_from_options as get_table
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Iterable, Literal
10
+ from linkmerce.common.extract import JsonObject
11
+
12
+
13
+ def get_module(name: str) -> str:
14
+ return (".naver.openapi" + name) if name.startswith('.') else name
15
+
16
+
17
+ def search_blog(
18
+ client_id: str,
19
+ client_secret: str,
20
+ query: str | Iterable[str],
21
+ start: int | Iterable[int] = 1,
22
+ display: int = 100,
23
+ sort: Literal["sim","date"] = "sim",
24
+ how: Literal["sync","async","async_loop"] = "sync",
25
+ return_type: Literal["csv","json","parquet","raw"] = "json",
26
+ extract_options: dict | None = None,
27
+ transform_options: dict | None = None,
28
+ ) -> JsonObject:
29
+ args = (query, start, display, sort)
30
+ table = get_table(transform_options, "table")
31
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
32
+ options = dict(extract_options=extract_options, transform_options=transform_options)
33
+ return run_with_duckdb(get_module(".search"), "BlogSearch", "BlogSearch", how, table, return_type, args, **options)
34
+
35
+
36
+ def search_news(
37
+ client_id: str,
38
+ client_secret: str,
39
+ query: str | Iterable[str],
40
+ start: int | Iterable[int] = 1,
41
+ display: int = 100,
42
+ sort: Literal["sim","date"] = "sim",
43
+ how: Literal["sync","async","async_loop"] = "sync",
44
+ return_type: Literal["csv","json","parquet","raw"] = "json",
45
+ extract_options: dict | None = None,
46
+ transform_options: dict | None = None,
47
+ ) -> JsonObject:
48
+ args = (query, start, display, sort)
49
+ table = get_table(transform_options, "table")
50
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
51
+ options = dict(extract_options=extract_options, transform_options=transform_options)
52
+ return run_with_duckdb(get_module(".search"), "NewsSearch", "NewsSearch", how, table, return_type, args, **options)
53
+
54
+
55
+ def search_book(
56
+ client_id: str,
57
+ client_secret: str,
58
+ query: str | Iterable[str],
59
+ start: int | Iterable[int] = 1,
60
+ display: int = 100,
61
+ sort: Literal["sim","date"] = "sim",
62
+ how: Literal["sync","async","async_loop"] = "sync",
63
+ return_type: Literal["csv","json","parquet","raw"] = "json",
64
+ extract_options: dict | None = None,
65
+ transform_options: dict | None = None,
66
+ ) -> JsonObject:
67
+ args = (query, start, display, sort)
68
+ table = get_table(transform_options, "table")
69
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
70
+ options = dict(extract_options=extract_options, transform_options=transform_options)
71
+ return run_with_duckdb(get_module(".search"), "BookSearch", "BookSearch", how, table, return_type, args, **options)
72
+
73
+
74
+ def search_cafe(
75
+ client_id: str,
76
+ client_secret: str,
77
+ query: str | Iterable[str],
78
+ start: int | Iterable[int] = 1,
79
+ display: int = 100,
80
+ sort: Literal["sim","date"] = "sim",
81
+ how: Literal["sync","async","async_loop"] = "sync",
82
+ return_type: Literal["csv","json","parquet","raw"] = "json",
83
+ extract_options: dict | None = None,
84
+ transform_options: dict | None = None,
85
+ ) -> JsonObject:
86
+ args = (query, start, display, sort)
87
+ table = get_table(transform_options, "table")
88
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
89
+ options = dict(extract_options=extract_options, transform_options=transform_options)
90
+ return run_with_duckdb(get_module(".search"), "CafeSearch", "CafeSearch", how, table, return_type, args, **options)
91
+
92
+
93
+ def search_kin(
94
+ client_id: str,
95
+ client_secret: str,
96
+ query: str | Iterable[str],
97
+ start: int | Iterable[int] = 1,
98
+ display: int = 100,
99
+ sort: Literal["sim","date","point"] = "sim",
100
+ how: Literal["sync","async","async_loop"] = "sync",
101
+ return_type: Literal["csv","json","parquet","raw"] = "json",
102
+ extract_options: dict | None = None,
103
+ transform_options: dict | None = None,
104
+ ) -> JsonObject:
105
+ args = (query, start, display, sort)
106
+ table = get_table(transform_options, "table")
107
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
108
+ options = dict(extract_options=extract_options, transform_options=transform_options)
109
+ return run_with_duckdb(get_module(".search"), "KiNSearch", "KiNSearch", how, table, return_type, args, **options)
110
+
111
+
112
+ def search_image(
113
+ client_id: str,
114
+ client_secret: str,
115
+ query: str | Iterable[str],
116
+ start: int | Iterable[int] = 1,
117
+ display: int = 100,
118
+ sort: Literal["sim","date"] = "sim",
119
+ filter: Literal["all","large","medium","small"] = "all",
120
+ how: Literal["sync","async","async_loop"] = "sync",
121
+ return_type: Literal["csv","json","parquet","raw"] = "json",
122
+ extract_options: dict | None = None,
123
+ transform_options: dict | None = None,
124
+ ) -> JsonObject:
125
+ args = (query, start, display, sort, filter)
126
+ table = get_table(transform_options, "table")
127
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
128
+ options = dict(extract_options=extract_options, transform_options=transform_options)
129
+ return run_with_duckdb(get_module(".search"), "ImageSearch", "ImageSearch", how, table, return_type, args, **options)
130
+
131
+
132
+ def search_shop(
133
+ client_id: str,
134
+ client_secret: str,
135
+ query: str | Iterable[str],
136
+ start: int | Iterable[int] = 1,
137
+ display: int = 100,
138
+ sort: Literal["sim","date","asc","dsc"] = "sim",
139
+ how: Literal["sync","async","async_loop"] = "sync",
140
+ return_type: Literal["csv","json","parquet","raw"] = "json",
141
+ extract_options: dict | None = None,
142
+ transform_options: dict | None = None,
143
+ ) -> JsonObject:
144
+ args = (query, start, display, sort)
145
+ table = get_table(transform_options, "table")
146
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
147
+ options = dict(extract_options=extract_options, transform_options=transform_options)
148
+ return run_with_duckdb(get_module(".search"), "ShoppingSearch", "ShoppingSearch", how, table, return_type, args, **options)
149
+
150
+
151
+ def rank_shop(
152
+ client_id: str,
153
+ client_secret: str,
154
+ query: str | Iterable[str],
155
+ start: int | Iterable[int] = 1,
156
+ display: int = 100,
157
+ sort: Literal["sim","date","asc","dsc"] = "sim",
158
+ how: Literal["sync","async","async_loop"] = "sync",
159
+ return_type: Literal["csv","json","parquet","raw"] = "json",
160
+ extract_options: dict | None = None,
161
+ transform_options: dict | None = None,
162
+ ) -> dict[str,JsonObject]:
163
+ args = (query, start, display, sort)
164
+ table = [get_table(transform_options, "rank_table"), get_table(transform_options, "product_table", "product")]
165
+ extract_options = dict(extract_options or dict(), variables=dict(client_id=client_id, client_secret=client_secret))
166
+ options = dict(extract_options=extract_options, transform_options=transform_options)
167
+ return run_with_duckdb(get_module(".search"), "ShoppingRank", "ShoppingRank", how, table, return_type, args, **options)
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ from linkmerce.common.api import run_with_duckdb
4
+ from linkmerce.common.api import get_table_from_options as get_table
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Iterable, Literal
10
+ from linkmerce.common.extract import JsonObject
11
+
12
+
13
+ def get_module(name: str) -> str:
14
+ return (".searchad.manage" + name) if name.startswith('.') else name
15
+
16
+
17
+ def exposure_diagnosis(
18
+ customer_id: str,
19
+ cookies: str,
20
+ keyword: str | Iterable[str],
21
+ domain: Literal["search","shopping"] = "search",
22
+ mobile: bool = True,
23
+ is_own: bool | None = None,
24
+ return_type: Literal["csv","json","parquet","raw"] = "json",
25
+ extract_options: dict | None = None,
26
+ transform_options: dict | None = None,
27
+ ) -> JsonObject:
28
+ args = (keyword, domain, mobile, is_own)
29
+ table = get_table(transform_options, "table")
30
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies), variables=dict(customer_id=customer_id))
31
+ options = dict(extract_options=extract_options, transform_options=transform_options)
32
+ return run_with_duckdb(get_module(".exposure"), "ExposureDiagnosis", "ExposureDiagnosis", "sync", table, return_type, args, **options)
33
+
34
+
35
+ def exposure_rank(
36
+ customer_id: str,
37
+ cookies: str,
38
+ keyword: str | Iterable[str],
39
+ domain: Literal["search","shopping"] = "search",
40
+ mobile: bool = True,
41
+ is_own: bool | None = None,
42
+ return_type: Literal["csv","json","parquet","raw"] = "json",
43
+ extract_options: dict | None = None,
44
+ transform_options: dict | None = None,
45
+ ) -> JsonObject:
46
+ args = (keyword, domain, mobile, is_own)
47
+ table = [get_table(transform_options, "rank_table"), get_table(transform_options, "product_table", "product")]
48
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies), variables=dict(customer_id=customer_id))
49
+ options = dict(extract_options=extract_options, transform_options=transform_options)
50
+ return run_with_duckdb(get_module(".exposure"), "ExposureRank", "ExposureRank", "sync", table, return_type, args, **options)
@@ -0,0 +1,154 @@
1
+ from __future__ import annotations
2
+
3
+ from linkmerce.common.api import run_with_duckdb
4
+ from linkmerce.common.api import get_table_from_options as get_table
5
+
6
+ from typing import TYPE_CHECKING
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Iterable, Literal
10
+ from linkmerce.common.extract import JsonObject
11
+ import datetime as dt
12
+
13
+
14
+ def get_module(name: str) -> str:
15
+ return (".smartstore.brand" + name) if name.startswith('.') else name
16
+
17
+
18
+ def brand_catalog(
19
+ cookies: str,
20
+ brand_ids: str | Iterable[str],
21
+ sort_type: Literal["popular","recent","price"] = "poular",
22
+ is_brand_catalog: bool | None = None,
23
+ page: int | list[int] | None = 0,
24
+ page_size: int = 100,
25
+ how: Literal["sync","async","async_loop"] = "sync",
26
+ return_type: Literal["csv","json","parquet","raw"] = "json",
27
+ extract_options: dict | None = None,
28
+ transform_options: dict | None = None,
29
+ ) -> JsonObject:
30
+ args = (brand_ids, sort_type, is_brand_catalog, page, page_size)
31
+ table = get_table(transform_options, "table")
32
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
33
+ options = dict(extract_options=extract_options, transform_options=transform_options)
34
+ return run_with_duckdb(get_module(".catalog"), "BrandCatalog", "BrandCatalog", how, table, return_type, args, **options)
35
+
36
+
37
+ def brand_product(
38
+ cookies: str,
39
+ brand_ids: str | Iterable[str],
40
+ mall_seq: int | str | Iterable[int | str] | None = None,
41
+ sort_type: Literal["popular","recent","price"] = "poular",
42
+ is_brand_catalog: bool | None = None,
43
+ page: int | list[int] | None = 0,
44
+ page_size: int = 100,
45
+ how: Literal["sync","async","async_loop"] = "sync",
46
+ return_type: Literal["csv","json","parquet","raw"] = "json",
47
+ extract_options: dict | None = None,
48
+ transform_options: dict | None = None,
49
+ ) -> JsonObject:
50
+ args = (brand_ids, mall_seq, sort_type, is_brand_catalog, page, page_size)
51
+ table = get_table(transform_options, "table")
52
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
53
+ options = dict(extract_options=extract_options, transform_options=transform_options)
54
+ return run_with_duckdb(get_module(".catalog"), "BrandProduct", "BrandProduct", how, table, return_type, args, **options)
55
+
56
+
57
+ def brand_price(
58
+ cookies: str,
59
+ brand_ids: str | Iterable[str],
60
+ mall_seq: int | str | Iterable[int | str],
61
+ sort_type: Literal["popular","recent","price"] = "recent",
62
+ is_brand_catalog: bool | None = None,
63
+ page: int | list[int] | None = 0,
64
+ page_size: int = 100,
65
+ how: Literal["sync","async","async_loop"] = "sync",
66
+ return_type: Literal["csv","json","parquet","raw"] = "json",
67
+ extract_options: dict | None = None,
68
+ transform_options: dict | None = None,
69
+ ) -> JsonObject:
70
+ args = (brand_ids, mall_seq, sort_type, is_brand_catalog, page, page_size)
71
+ table = [get_table(transform_options, "price_table"), get_table(transform_options, "product_table", "product")]
72
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
73
+ options = dict(extract_options=extract_options, transform_options=transform_options)
74
+ return run_with_duckdb(get_module(".catalog"), "BrandPrice", "BrandPrice", how, table, return_type, args, **options)
75
+
76
+
77
+ def store_sales(
78
+ cookies: str,
79
+ mall_seq: int | str | Iterable[int | str],
80
+ start_date: dt.date | str,
81
+ end_date: dt.date | str,
82
+ date_type: Literal["daily","weekly","monthly"] = "daily",
83
+ page: int | Iterable[int] = 1,
84
+ page_size: int = 1000,
85
+ how: Literal["sync","async","async_loop"] = "sync",
86
+ return_type: Literal["csv","json","parquet","raw"] = "json",
87
+ extract_options: dict | None = None,
88
+ transform_options: dict | None = None,
89
+ ) -> JsonObject:
90
+ args = (mall_seq, start_date, end_date, date_type, page, page_size)
91
+ table = get_table(transform_options, "table")
92
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
93
+ options = dict(extract_options=extract_options, transform_options=transform_options)
94
+ return run_with_duckdb(get_module(".sales"), "StoreSales", "StoreSales", how, table, return_type, args, **options)
95
+
96
+
97
+ def category_sales(
98
+ cookies: str,
99
+ mall_seq: int | str | Iterable[int | str],
100
+ start_date: dt.date | str,
101
+ end_date: dt.date | str,
102
+ date_type: Literal["daily","weekly","monthly"] = "daily",
103
+ page: int | Iterable[int] = 1,
104
+ page_size: int = 1000,
105
+ how: Literal["sync","async","async_loop"] = "sync",
106
+ return_type: Literal["csv","json","parquet","raw"] = "json",
107
+ extract_options: dict | None = None,
108
+ transform_options: dict | None = None,
109
+ ) -> JsonObject:
110
+ args = (mall_seq, start_date, end_date, date_type, page, page_size)
111
+ table = get_table(transform_options, "table")
112
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
113
+ options = dict(extract_options=extract_options, transform_options=transform_options)
114
+ return run_with_duckdb(get_module(".sales"), "CategorySales", "CategorySales", how, table, return_type, args, **options)
115
+
116
+
117
+ def product_sales(
118
+ cookies: str,
119
+ mall_seq: int | str | Iterable[int | str],
120
+ start_date: dt.date | str,
121
+ end_date: dt.date | str,
122
+ date_type: Literal["daily","weekly","monthly"] = "daily",
123
+ page: int | Iterable[int] = 1,
124
+ page_size: int = 1000,
125
+ how: Literal["sync","async","async_loop"] = "sync",
126
+ return_type: Literal["csv","json","parquet","raw"] = "json",
127
+ extract_options: dict | None = None,
128
+ transform_options: dict | None = None,
129
+ ) -> JsonObject:
130
+ args = (mall_seq, start_date, end_date, date_type, page, page_size)
131
+ table = get_table(transform_options, "table")
132
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
133
+ options = dict(extract_options=extract_options, transform_options=transform_options)
134
+ return run_with_duckdb(get_module(".sales"), "ProductSales", "ProductSales", how, table, return_type, args, **options)
135
+
136
+
137
+ def aggregated_sales(
138
+ cookies: str,
139
+ mall_seq: int | str | Iterable[int | str],
140
+ start_date: dt.date | str,
141
+ end_date: dt.date | str,
142
+ date_type: Literal["daily","weekly","monthly"] = "daily",
143
+ page: int | Iterable[int] = 1,
144
+ page_size: int = 1000,
145
+ how: Literal["sync","async","async_loop"] = "sync",
146
+ return_type: Literal["csv","json","parquet","raw"] = "json",
147
+ extract_options: dict | None = None,
148
+ transform_options: dict | None = None,
149
+ ) -> JsonObject:
150
+ args = (mall_seq, start_date, end_date, date_type, page, page_size)
151
+ table = [get_table(transform_options, "sales_table"), get_table(transform_options, "product_table", "product")]
152
+ extract_options = dict(extract_options or dict(), headers=dict(cookies=cookies))
153
+ options = dict(extract_options=extract_options, transform_options=transform_options)
154
+ return run_with_duckdb(get_module(".sales"), "AggregatedSales", "AggregatedSales", how, table, return_type, args, **options)
@@ -0,0 +1,156 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Sequence, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from typing import Any, Literal, Type
7
+ from linkmerce.common.extract import Extractor, JsonObject
8
+ from linkmerce.common.transform import Transformer, DBTransformer, DuckDBTransformer
9
+
10
+
11
+ ###################################################################
12
+ ############################## Import #############################
13
+ ###################################################################
14
+
15
+ def import_extractor(module: str, attr: str) -> Type[Extractor]:
16
+ from importlib import import_module
17
+ obj = import_module(_join(module, "extract"))
18
+ return getattr(obj, attr)
19
+
20
+
21
+ def import_transformer(module: str, attr: str) -> Type[Transformer]:
22
+ from importlib import import_module
23
+ obj = import_module(_join(module, "transform"))
24
+ return getattr(obj, attr)
25
+
26
+
27
+ def import_dbt(module: str | None = None, attr: str | None = None) -> Type[DBTransformer]:
28
+ if module and attr:
29
+ return import_transformer(module, attr)
30
+ else:
31
+ from importlib import import_module
32
+ module = import_module("linkmerce.common.transform")
33
+ return getattr(module, "DummyDBTransformer")
34
+
35
+
36
+ def _join(path: str, name: str) -> str:
37
+ if path.startswith('.'):
38
+ path = "linkmerce.core" + path
39
+ return path + '.' + name
40
+
41
+
42
+ ###################################################################
43
+ ############################### Run ###############################
44
+ ###################################################################
45
+
46
+ def run(
47
+ module: str,
48
+ extractor: str,
49
+ transformer: str | None = None,
50
+ how: Literal["sync","async","async_loop"] = "sync",
51
+ args: tuple = tuple(),
52
+ kwargs: dict = dict(),
53
+ extract_options: dict | None = None,
54
+ transform_options: dict | None = None,
55
+ ) -> Any:
56
+ extract_options = extract_options.copy() if isinstance(extract_options, dict) else dict()
57
+ if transformer and ("parser" not in extract_options):
58
+ transformer_ = import_transformer(module, transformer)(**(transform_options or dict()))
59
+ extract_options["parser"] = transformer_.transform
60
+ extractor_ = import_extractor(module, extractor)(**extract_options)
61
+ return _extract(extractor_, how, args, kwargs)
62
+
63
+
64
+ def run_with_connection(
65
+ module: str,
66
+ extractor: str,
67
+ transformer: str | None = None,
68
+ how: Literal["sync","async","async_loop"] = "sync",
69
+ args: tuple = tuple(),
70
+ kwargs: dict = dict(),
71
+ extract_options: dict | None = None,
72
+ transform_options: dict | None = None,
73
+ ) -> Any:
74
+ extract_options = extract_options.copy() if isinstance(extract_options, dict) else dict()
75
+ has_parser = ("parser" not in extract_options)
76
+ with import_dbt(module if has_parser else None, transformer)(**(transform_options or dict())) as transformer_:
77
+ if transformer and has_parser:
78
+ extract_options["parser"] = transformer_.transform
79
+ extractor_ = import_extractor(module, extractor)(**extract_options)
80
+ return _extract(extractor_, how, args, kwargs)
81
+
82
+
83
+ def _extract(
84
+ extractor: Extractor,
85
+ how: Literal["sync","async","async_loop"] = "sync",
86
+ args: tuple = tuple(),
87
+ kwargs: dict = dict(),
88
+ ) -> Any:
89
+ if how == "sync":
90
+ return extractor.extract(*args, **kwargs)
91
+ elif how == "async":
92
+ import asyncio
93
+ return asyncio.run(extractor.extract_async(*args, **kwargs))
94
+ elif how == "async_loop":
95
+ import asyncio, nest_asyncio
96
+ nest_asyncio.apply()
97
+ loop = asyncio.get_running_loop()
98
+ task = asyncio.create_task(extractor.extract_async(*args, **kwargs))
99
+ return loop.run_until_complete(task)
100
+ else:
101
+ raise ValueError("Invalid value for how to run. Supported values are: sync, async, async_loop.")
102
+
103
+
104
+ ###################################################################
105
+ ######################### Run with DuckDB #########################
106
+ ###################################################################
107
+
108
+ def run_with_duckdb(
109
+ module: str,
110
+ extractor: str,
111
+ transformer: str | None = None,
112
+ how: Literal["sync","async","async_loop"] = "sync",
113
+ table: str | Sequence[str] = ":default:",
114
+ return_type: Literal["raw","csv","json","parquet"] = "json",
115
+ args: tuple = tuple(),
116
+ kwargs: dict = dict(),
117
+ extract_options: dict | None = None,
118
+ transform_options: dict | None = None,
119
+ ) -> Any | dict[str,Any]:
120
+ extract_options = extract_options.copy() if isinstance(extract_options, dict) else dict()
121
+ has_parser = not ((return_type == "raw") or ("parser" in extract_options))
122
+ with import_dbt(module if has_parser else None, transformer)(**(transform_options or dict())) as transformer_:
123
+ if transformer and has_parser:
124
+ extract_options["parser"] = transformer_.transform
125
+ extractor_ = import_extractor(module, extractor)(**extract_options)
126
+ results = _extract(extractor_, how, args, kwargs)
127
+ return _fetch_all_from_table(transformer_, results, table, return_type)
128
+
129
+
130
+ def get_table_from_options(transform_options: dict | None = None, key: str = "table", default: str = ":default:") -> str:
131
+ if isinstance(transform_options, dict):
132
+ create_options = transform_options.get("create_options")
133
+ if isinstance(create_options, dict):
134
+ return create_options.get(key, default)
135
+ return default
136
+
137
+
138
+ def _get_table(transformer: DuckDBTransformer, table: str = ":default:") -> str:
139
+ return transformer.default_table if table == ":default:" else table
140
+
141
+
142
+ def _fetch_all_from_table(
143
+ transformer: DuckDBTransformer,
144
+ results: JsonObject,
145
+ table: str | Sequence[str] = ":default:",
146
+ return_type: Literal["raw","csv","json","parquet"] = "json",
147
+ ) -> Any | dict[str,Any]:
148
+ if (return_type == "raw") or (transformer.conn is None):
149
+ return results
150
+ fetch_all = getattr(transformer, f"fetch_all_to_{return_type}")
151
+ if isinstance(table, str):
152
+ return fetch_all(_get_table(transformer, table))
153
+ elif isinstance(table, Sequence):
154
+ return {_get_table(transformer, table_): fetch_all(table_) for table_ in table}
155
+ else:
156
+ raise TypeError("Invalid type for table. A string or sequence type is allowed.")
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ class AuthenticationError(RuntimeError):
5
+ ...
6
+
7
+
8
+ class ParseError(ValueError):
9
+ ...
10
+
11
+
12
+ class RequestError(RuntimeError):
13
+ ...
14
+
15
+
16
+ class UnauthorizedError(RuntimeError):
17
+ ...