linkmerce 0.1.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.
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.3
2
+ Name: linkmerce
3
+ Version: 0.1.0
4
+ Summary: E-commerce API integration management
5
+ Requires-Dist: aiohttp>=3.12.14
6
+ Requires-Dist: asyncio>=3.4.3
7
+ Requires-Dist: bs4>=0.0.2
8
+ Requires-Dist: lxml>=6.0.0
9
+ Requires-Dist: openpyxl>=3.1.5
10
+ Requires-Dist: pandas>=2.3.1
11
+ Requires-Dist: requests>=2.32.4
12
+ Requires-Dist: xlrd>=2.0.2
13
+ Requires-Python: >=3.10
14
+ Description-Content-Type: text/markdown
15
+
16
+ # LinkMerce
17
+ - E-commerce API integration management
@@ -0,0 +1,2 @@
1
+ # LinkMerce
2
+ - E-commerce API integration management
Binary file
File without changes
Binary file
@@ -0,0 +1,3 @@
1
+ from collect.base import Collector
2
+ from collect.base import JsonObject
3
+ from collect.base import GET, POST, OPTIONS, HEAD, PUT, PATCH, DELETE
@@ -0,0 +1,317 @@
1
+ from __future__ import annotations
2
+ from abc import ABCMeta, abstractmethod
3
+ import functools
4
+
5
+ from typing import Dict, List, Union, TYPE_CHECKING
6
+ JsonObject = Union[Dict, List]
7
+
8
+ if TYPE_CHECKING:
9
+ from typing import Any, IO, Literal, Sequence, Tuple
10
+ from requests import Session, Response
11
+ from requests.cookies import RequestsCookieJar
12
+ from aiohttp.client import ClientSession, ClientResponse
13
+ from aiohttp.typedefs import LooseCookies
14
+ from bs4 import BeautifulSoup
15
+ from pandas import DataFrame
16
+ JsonSerialize = Union[Dict, List, bytes, IO]
17
+
18
+
19
+ GET = "GET"
20
+ POST = "POST"
21
+ OPTIONS = "OPTIONS"
22
+ HEAD = "HEAD"
23
+ PUT = "PUT"
24
+ PATCH = "PATCH"
25
+ DELETE = "DELETE"
26
+
27
+
28
+ class BaseSessionClient(metaclass=ABCMeta):
29
+ method = GET
30
+ url = str()
31
+
32
+ def __init__(self, session: Session | ClientSession | None = None, **kwargs):
33
+ self.init_session(session)
34
+ self.init_constant(**kwargs)
35
+
36
+ def init_session(self, session: Session | ClientSession | None = None):
37
+ self.session = session
38
+
39
+ def init_constant(self, **kwargs):
40
+ self.set_request_headers(**kwargs)
41
+
42
+ @abstractmethod
43
+ def request(self, **kwargs):
44
+ raise NotImplementedError("The request method must be implemented.")
45
+
46
+ def set_request_headers(self,
47
+ authority: str = str(),
48
+ accept: str = "*/*",
49
+ encoding: str = "gzip, deflate, br",
50
+ language: Literal["ko","en"] | str = "ko",
51
+ connection: str = "keep-alive",
52
+ contents: Literal["form", "javascript", "json", "text", "multipart"] | str | Dict = str(),
53
+ cookies: str = str(),
54
+ host: str = str(),
55
+ origin: str = str(),
56
+ priority: str = "u=0, i",
57
+ referer: str = str(),
58
+ client: str = str(),
59
+ mobile: bool = False,
60
+ platform: str = str(),
61
+ metadata: Literal["cors", "navigate"] | Dict[str,str] = "navigate",
62
+ https: bool = False,
63
+ user_agent: str = str(),
64
+ ajax: bool = False,
65
+ **kwargs):
66
+ from utils.headers import make_headers
67
+ kwargs = {key: value for key, value in locals().items() if key not in ("self","kwargs","make_headers")}
68
+ self.headers = make_headers(**kwargs)
69
+
70
+ def get_request_headers(self, **kwargs) -> Dict[str,str]:
71
+ return dict(self.headers, **kwargs) if kwargs else self.headers
72
+
73
+
74
+ class RequestsSessionClient(BaseSessionClient):
75
+ def request(self,
76
+ method: str,
77
+ url: str,
78
+ params: Dict | List[Tuple] | bytes | None = None,
79
+ data: Dict | List[Tuple] | bytes | IO | None = None,
80
+ json: JsonSerialize | None = None,
81
+ headers: Dict[str,str] = None,
82
+ cookies: Dict | RequestsCookieJar = None,
83
+ **kwargs) -> Response:
84
+ return self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
85
+
86
+ def request_status(self,
87
+ method: str,
88
+ url: str,
89
+ params: Dict | List[Tuple] | bytes | None = None,
90
+ data: Dict | List[Tuple] | bytes | IO | None = None,
91
+ json: JsonSerialize | None = None,
92
+ headers: Dict[str,str] = None,
93
+ cookies: Dict | RequestsCookieJar = None,
94
+ **kwargs) -> int:
95
+ with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
96
+ return response.status_code
97
+
98
+ def request_content(self,
99
+ method: str,
100
+ url: str,
101
+ params: Dict | List[Tuple] | bytes | None = None,
102
+ data: Dict | List[Tuple] | bytes | IO | None = None,
103
+ json: JsonSerialize | None = None,
104
+ headers: Dict[str,str] = None,
105
+ cookies: Dict | RequestsCookieJar = None,
106
+ **kwargs) -> bytes:
107
+ with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
108
+ return response.content
109
+
110
+ def request_text(self,
111
+ method: str,
112
+ url: str,
113
+ params: Dict | List[Tuple] | bytes | None = None,
114
+ data: Dict | List[Tuple] | bytes | IO | None = None,
115
+ json: JsonSerialize | None = None,
116
+ headers: Dict[str,str] = None,
117
+ cookies: Dict | RequestsCookieJar = None,
118
+ **kwargs) -> str:
119
+ with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
120
+ return response.text
121
+
122
+ def request_json(self,
123
+ method: str,
124
+ url: str,
125
+ params: Dict | List[Tuple] | bytes | None = None,
126
+ data: Dict | List[Tuple] | bytes | IO | None = None,
127
+ json: JsonSerialize | None = None,
128
+ headers: Dict[str,str] = None,
129
+ cookies: Dict | RequestsCookieJar = None,
130
+ **kwargs) -> JsonObject:
131
+ with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
132
+ return response.json()
133
+
134
+ def request_headers(self,
135
+ method: str,
136
+ url: str,
137
+ params: Dict | List[Tuple] | bytes | None = None,
138
+ data: Dict | List[Tuple] | bytes | IO | None = None,
139
+ json: JsonSerialize | None = None,
140
+ headers: Dict[str,str] = None,
141
+ cookies: Dict | RequestsCookieJar = None,
142
+ **kwargs) -> Dict[str,str]:
143
+ with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
144
+ return response.headers
145
+
146
+ def request_html(self,
147
+ method: str,
148
+ url: str,
149
+ params: Dict | List[Tuple] | bytes | None = None,
150
+ data: Dict | List[Tuple] | bytes | IO | None = None,
151
+ json: JsonSerialize | None = None,
152
+ headers: Dict[str,str] = None,
153
+ cookies: Dict | RequestsCookieJar = None,
154
+ features: str | Sequence[str] | None = "html.parser",
155
+ **kwargs) -> BeautifulSoup:
156
+ from bs4 import BeautifulSoup
157
+ response = self.request_text(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
158
+ return BeautifulSoup(response, features)
159
+
160
+ def request_table(self,
161
+ method: str,
162
+ url: str,
163
+ params: Dict | List[Tuple] | bytes | None = None,
164
+ data: Dict | List[Tuple] | bytes | IO | None = None,
165
+ json: JsonSerialize | None = None,
166
+ headers: Dict[str,str] = None,
167
+ cookies: Dict | RequestsCookieJar = None,
168
+ content_type: Literal["excel", "csv", "html", "xml"] | Sequence = "xlsx",
169
+ table_options: Dict = dict(),
170
+ **kwargs) -> DataFrame:
171
+ from utils.pandas import read_table
172
+ response = self.request_content(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
173
+ return read_table(response, table_format=content_type, **table_options)
174
+
175
+
176
+ class AiohttpSessionClient(BaseSessionClient):
177
+ def request(self, *args, **kwargs):
178
+ raise NotImplementedError("This feature does not support synchronous requests. Please use the request_async method instead.")
179
+
180
+ async def request_async(self,
181
+ method: str,
182
+ url: str,
183
+ params: Dict | List[Tuple] | bytes | None = None,
184
+ data: Dict | List[Tuple] | bytes | IO | None = None,
185
+ json: JsonSerialize | None = None,
186
+ headers: Dict[str,str] = None,
187
+ cookies: Dict | LooseCookies = None,
188
+ **kwargs) -> ClientResponse:
189
+ return await self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
190
+
191
+ async def request_async_status(self,
192
+ method: str,
193
+ url: str,
194
+ params: Dict | List[Tuple] | bytes | None = None,
195
+ data: Dict | List[Tuple] | bytes | IO | None = None,
196
+ json: JsonSerialize | None = None,
197
+ headers: Dict[str,str] = None,
198
+ cookies: Dict | LooseCookies = None,
199
+ **kwargs) -> int:
200
+ async with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
201
+ return response.status
202
+
203
+ async def request_async_content(self,
204
+ method: str,
205
+ url: str,
206
+ params: Dict | List[Tuple] | bytes | None = None,
207
+ data: Dict | List[Tuple] | bytes | IO | None = None,
208
+ json: JsonSerialize | None = None,
209
+ headers: Dict[str,str] = None,
210
+ cookies: Dict | LooseCookies = None,
211
+ **kwargs) -> bytes:
212
+ async with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
213
+ return response.content
214
+
215
+ async def request_async_text(self,
216
+ method: str,
217
+ url: str,
218
+ params: Dict | List[Tuple] | bytes | None = None,
219
+ data: Dict | List[Tuple] | bytes | IO | None = None,
220
+ json: JsonSerialize | None = None,
221
+ headers: Dict[str,str] = None,
222
+ cookies: Dict | LooseCookies = None,
223
+ **kwargs) -> str:
224
+ async with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
225
+ return await response.text()
226
+
227
+ async def request_async_json(self,
228
+ method: str,
229
+ url: str,
230
+ params: Dict | List[Tuple] | bytes | None = None,
231
+ data: Dict | List[Tuple] | bytes | IO | None = None,
232
+ json: JsonSerialize | None = None,
233
+ headers: Dict[str,str] = None,
234
+ cookies: Dict | LooseCookies = None,
235
+ **kwargs) -> JsonObject:
236
+ async with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
237
+ return await response.json()
238
+
239
+ async def request_async_headers(self,
240
+ method: str,
241
+ url: str,
242
+ params: Dict | List[Tuple] | bytes | None = None,
243
+ data: Dict | List[Tuple] | bytes | IO | None = None,
244
+ json: JsonSerialize | None = None,
245
+ headers: Dict[str,str] = None,
246
+ cookies: Dict | LooseCookies = None,
247
+ **kwargs) -> Dict[str,str]:
248
+ async with self.session.request(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs) as response:
249
+ return response.headers
250
+
251
+ async def request_async_html(self,
252
+ method: str,
253
+ url: str,
254
+ params: Dict | List[Tuple] | bytes | None = None,
255
+ data: Dict | List[Tuple] | bytes | IO | None = None,
256
+ json: JsonSerialize | None = None,
257
+ headers: Dict[str,str] = None,
258
+ cookies: Dict | LooseCookies = None,
259
+ features: str | Sequence[str] | None = "html.parser",
260
+ **kwargs) -> BeautifulSoup:
261
+ from bs4 import BeautifulSoup
262
+ response = await self.request_async_text(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
263
+ return BeautifulSoup(response, features)
264
+
265
+ async def request_async_table(self,
266
+ method: str,
267
+ url: str,
268
+ params: Dict | List[Tuple] | bytes | None = None,
269
+ data: Dict | List[Tuple] | bytes | IO | None = None,
270
+ json: JsonSerialize | None = None,
271
+ headers: Dict[str,str] = None,
272
+ cookies: Dict | LooseCookies = None,
273
+ content_type: Literal["excel", "csv", "html", "xml"] | Sequence = "xlsx",
274
+ table_options: Dict = dict(),
275
+ **kwargs) -> DataFrame:
276
+ from utils.pandas import read_table
277
+ response = await self.request_async_content(method, url, params=params, data=data, json=json, headers=headers, cookies=cookies, **kwargs)
278
+ return read_table(response, table_format=content_type, **table_options)
279
+
280
+
281
+ class Collector(RequestsSessionClient, AiohttpSessionClient, metaclass=ABCMeta):
282
+ @abstractmethod
283
+ def collect(self, *args, **kwargs) -> Any:
284
+ raise NotImplementedError("This feature does not support synchronous requests. Please use the collect_async method instead.")
285
+
286
+ def with_session(func):
287
+ @functools.wraps(func)
288
+ def wrapper(self: Collector, *args, init_session=False, **context):
289
+ if init_session and (self.session is None):
290
+ import requests
291
+ with requests.Session() as session:
292
+ self.init_session(session)
293
+ return func(self, *args, **context)
294
+ else:
295
+ return func(self, *args, **context)
296
+ return wrapper
297
+
298
+ async def collect_async(self, *args, **kwargs):
299
+ raise NotImplementedError("This feature does not support asynchronous requests. Please use the collect method instead.")
300
+
301
+ def with_client_session(func):
302
+ @functools.wraps(func)
303
+ async def wrapper(self: Collector, *args, init_session=False, **context):
304
+ if init_session and (self.session is None):
305
+ import aiohttp
306
+ async with aiohttp.ClientSession() as session:
307
+ self.init_session(session)
308
+ return await func(self, *args, **context)
309
+ else:
310
+ return await func(self, *args, **context)
311
+ return wrapper
312
+
313
+ def get_request_message(self, *args, **kwargs) -> Dict:
314
+ raise NotImplementedError("The get_request_message is not implemented.")
315
+
316
+ def parse(self, response: Any, *args, **kwargs) -> Any:
317
+ raise NotImplementedError("The parse method is not implemented.")
File without changes
@@ -0,0 +1,24 @@
1
+ [project]
2
+ name = "linkmerce"
3
+ version = "0.1.0"
4
+ description = "E-commerce API integration management"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ dependencies = [
8
+ "aiohttp>=3.12.14",
9
+ "asyncio>=3.4.3",
10
+ "bs4>=0.0.2",
11
+ "lxml>=6.0.0",
12
+ "openpyxl>=3.1.5",
13
+ "pandas>=2.3.1",
14
+ "requests>=2.32.4",
15
+ "xlrd>=2.0.2",
16
+ ]
17
+
18
+ [build-system]
19
+ requires = ["uv_build>=0.8.0,<0.9.0"]
20
+ build-backend = "uv_build"
21
+
22
+ [tool.uv.build-backend]
23
+ module-name = ["auth", "collect", "object", "utils"]
24
+ module-root = ""
File without changes
@@ -0,0 +1,113 @@
1
+ from __future__ import annotations
2
+ from typing import Dict, TYPE_CHECKING
3
+
4
+ if TYPE_CHECKING:
5
+ from typing import Literal
6
+
7
+
8
+ CHROME_VERSION = 138
9
+
10
+
11
+ def make_headers(
12
+ authority: str = str(),
13
+ accept: str = "*/*",
14
+ encoding: str = "gzip, deflate, br",
15
+ language: Literal["ko","en"] | str = "ko",
16
+ connection: str = "keep-alive",
17
+ contents: Literal["form", "javascript", "json", "text", "multipart"] | str | Dict = str(),
18
+ cookies: str = str(),
19
+ host: str = str(),
20
+ origin: str = str(),
21
+ priority: str = "u=0, i",
22
+ referer: str = str(),
23
+ client: str = str(),
24
+ mobile: bool = False,
25
+ platform: str = str(),
26
+ metadata: Literal["cors", "navigate"] | Dict[str,str] = "cors",
27
+ https: bool = False,
28
+ user_agent: str = str(),
29
+ ajax: bool = False,
30
+ version: int = CHROME_VERSION
31
+ ) -> Dict[str,str]:
32
+ from urllib.parse import urlparse
33
+ return {
34
+ **({"authority": authority} if authority else dict()),
35
+ **({"accept": accept} if accept else dict()),
36
+ **({"accept-encoding": encoding} if encoding else dict()),
37
+ **({"accept-language": _get_default_language(language)} if language else dict()),
38
+ **({"connection": connection} if connection else dict()),
39
+ **({"content-type": _get_content_type(contents)} if contents else dict()),
40
+ **({"cookie": cookies} if cookies else dict()),
41
+ **({"host": urlparse(host).hostname} if host else dict()),
42
+ **({"origin": origin} if origin else dict()),
43
+ **({"priority": priority} if priority else dict()),
44
+ **({"referer": referer} if referer else dict()),
45
+ "sec-ch-ua": (client or _get_default_client(version)),
46
+ "sec-ch-ua-mobile": f"?{int(mobile)}",
47
+ "sec-ch-ua-platform": (platform or _get_current_platform()),
48
+ **_get_fetch_metadata(metadata),
49
+ **({"upgrade-insecure-requests": "1"} if https else dict()),
50
+ "user-agent": (user_agent or _get_user_agent(version)),
51
+ **({"X-Requested-With": "XMLHttpRequest"} if ajax else dict())
52
+ }
53
+
54
+
55
+ def _get_default_language(value: Literal["ko","en"] | str = "ko") -> str:
56
+ if value == "ko":
57
+ return "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7"
58
+ elif value == "en":
59
+ return "en-US,en;q=0.9"
60
+ else:
61
+ return value
62
+
63
+
64
+ def _get_content_type(contents: Literal["form", "javascript", "json", "text", "multipart"] | str | Dict):
65
+ if isinstance(contents, str):
66
+ if contents == "form":
67
+ return "application/x-www-form-urlencoded"
68
+ elif contents == "javascript":
69
+ return "javascript"
70
+ elif contents == "json":
71
+ return "application/json"
72
+ elif contents == "text":
73
+ return "text/plain"
74
+ elif contents == "multipart":
75
+ return "multipart/form-data"
76
+ else:
77
+ return contents
78
+ elif isinstance(contents, Dict):
79
+ content_type = _get_content_type(contents["type"])
80
+ for key, value in contents.items(): # boundary, charset, ...
81
+ if key != "type":
82
+ content_type += f"; {key}={value}"
83
+ return content_type
84
+ else:
85
+ raise TypeError("Invalid type for contents. A string or dictionary type is allowed.")
86
+
87
+
88
+ def _get_default_client(version: int = CHROME_VERSION) -> str:
89
+ return f'"Not)A;Brand";v="8", "Chromium";v="{version}", "Google Chrome";v="{version}"'
90
+
91
+
92
+ def _get_current_platform() -> str:
93
+ import platform
94
+ os_name = platform.system()
95
+ return "macOS" if os_name == "Darwin" else os_name
96
+
97
+
98
+ def _get_fetch_metadata(metadata: Literal["cors", "navigate"] | Dict[str,str] = "navigate") -> Dict[str,str]:
99
+ if isinstance(metadata, str):
100
+ if metadata == "cors":
101
+ return {"sec-fetch-dest": "empty", "sec-fetch-mode": "cors", "sec-fetch-site": "same-origin", "sec-fetch-user": "?1"}
102
+ elif metadata == "navigate":
103
+ return {"sec-fetch-dest": "document", "sec-fetch-mode": "navigate", "sec-fetch-site": "none", "sec-fetch-user": "?1"}
104
+ else:
105
+ return dict()
106
+ elif isinstance(metadata, Dict):
107
+ return metadata
108
+ else:
109
+ return dict()
110
+
111
+
112
+ def _get_user_agent(version: int = CHROME_VERSION) -> str:
113
+ return f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/{version}.0.0.0 Safari/537.36"
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+ import os
3
+
4
+ from typing import Iterable, Sequence, TYPE_CHECKING
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import Any, Callable, Dict, Hashable, List, Literal, Union
8
+ from io import BytesIO, StringIO
9
+ from pandas import DataFrame
10
+ from pandas._typing import DtypeArg
11
+ IndexLabel = Union[Sequence[Hashable], Hashable]
12
+
13
+
14
+ TABLE_FORMAT = {".xlsx", ".xls", ".xlsm", ".xlsb", ".csv", ".html", ".xml"}
15
+
16
+
17
+ def select_table(data: DataFrame, cols: IndexLabel, dropna: bool=False, reorder: bool=True) -> DataFrame:
18
+ from_cols, to_cols = (data.columns, _to_sequence(cols)) if reorder else (_to_sequence(cols), data.columns)
19
+ if dropna:
20
+ to_cols = [col for col in to_cols if col in from_cols]
21
+ else:
22
+ for col in cols:
23
+ if col not in data:
24
+ data[col] = None
25
+ return data[to_cols]
26
+
27
+
28
+ ###################################################################
29
+ ############################ Read Table ###########################
30
+ ###################################################################
31
+
32
+ def read_table(
33
+ io: bytes | str,
34
+ table_format: Literal["excel", "csv", "html", "xml"] | Sequence = "xlsx",
35
+ sheet_name: str | int | List | None = 0,
36
+ header: int | Sequence[int] | None = 0,
37
+ dtype: DtypeArg | None = None,
38
+ engine: Literal["xlrd", "openpyxl", "odf", "pyxlsb", "calamine"] | None = None,
39
+ parse_dates: bool | Sequence[int] | Sequence[Sequence[str] | Sequence[int]] | Dict[str, Sequence[int] | list[str]] = None,
40
+ file_pattern: Dict | None = None,
41
+ **kwargs
42
+ ) -> DataFrame:
43
+ import pandas as pd
44
+
45
+ def read_io(io: BytesIO | StringIO, format: Literal["excel", "csv", "html", "xml"]) -> DataFrame:
46
+ if format == "excel":
47
+ if engine == "xlrd":
48
+ from xlrd import open_workbook
49
+ with open_workbook(file_contents=io.getvalue(), logfile=open(os.devnull, 'w')) as wb:
50
+ return pd.read_excel(wb, engine="xlrd", **kwargs)
51
+ else:
52
+ return pd.read_excel(io, header=header, dtype=dtype, engine=engine, parse_dates=parse_dates, **kwargs)
53
+ elif format == "csv":
54
+ return pd.read_csv(io, header=header, dtype=dtype, parse_dates=parse_dates, **kwargs)
55
+ elif format == "html":
56
+ index = sheet_name if isinstance(sheet_name, int) else 0
57
+ return pd.read_html(io, header=header, parse_dates=parse_dates, **kwargs)[index]
58
+ elif format == "xml":
59
+ return pd.read_xml(io, parse_dates=parse_dates, **kwargs)
60
+ else:
61
+ raise ValueError("Invalid value for table_format. Supported formats are: excel, csv, html, xml.")
62
+
63
+ if isinstance(table_format, str):
64
+ return read_io(_read_bytes(io, file_pattern), table_format)
65
+ elif isinstance(table_format, Sequence):
66
+ return _try_sequence(read_io,
67
+ kwargs = dict(format=_to_sequence(table_format)),
68
+ persist = dict(io=_read_bytes(io, file_pattern)))
69
+ else:
70
+ raise TypeError("Invalid type for table_format. A string or sequence type is allowed.")
71
+
72
+
73
+ def _read_bytes(io: bytes | str, file_pattern: Dict | None=None) -> BytesIO | StringIO:
74
+ from io import BytesIO, StringIO
75
+ if isinstance(io, str):
76
+ if (os.path.splitext(io)[1] in TABLE_FORMAT):
77
+ with open(_get_file_path(io, **(file_pattern or dict())), 'rb') as file:
78
+ return BytesIO(file.read())
79
+ else:
80
+ return StringIO(io)
81
+ else:
82
+ return BytesIO(io)
83
+
84
+
85
+ def _get_file_path(io: str, regex: bool=False, index: int=0, **kwargs) -> str:
86
+ if regex:
87
+ import re
88
+ root, pattern = os.path.split(io)
89
+ def search(file_name: str) -> bool:
90
+ return bool(re.search(pattern, file_name))
91
+ filtered = list(filter(search, os.listdir(root or '.')))
92
+ io = os.path.join(root, filtered[index]) if -len(filtered) <= index < len(filtered) else str()
93
+ if os.path.exists(io):
94
+ return io
95
+ else:
96
+ raise ValueError("File path does not exist: {}".format(f"'{io}'" if isinstance(io, str) else "None"))
97
+
98
+
99
+ ###################################################################
100
+ ############################# General #############################
101
+ ###################################################################
102
+
103
+ def _to_sequence(cols: IndexLabel) -> Iterable[Hashable]:
104
+ return [cols] if isinstance(cols, str) or not isinstance(cols, Iterable) else cols
105
+
106
+
107
+ def _try_sequence(func: Callable, kwargs: Dict[str,Sequence]=dict(), persist: Dict=dict()) -> Any:
108
+ from itertools import product
109
+ args = list(map(lambda values: dict(zip(kwargs.keys(), values)),list(product(*kwargs.values()))))
110
+ for __m in args[:-1]:
111
+ try:
112
+ return func(**__m, **persist)
113
+ except:
114
+ pass
115
+ return func(**args[-1], **persist)