fyron 0.1.0__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.
- fyron/__init__.py +65 -0
- fyron/core/__init__.py +14 -0
- fyron/core/env.py +37 -0
- fyron/core/io.py +394 -0
- fyron/dicom/__init__.py +5 -0
- fyron/dicom/client.py +523 -0
- fyron/documents/__init__.py +5 -0
- fyron/documents/client.py +278 -0
- fyron/fhir/__init__.py +49 -0
- fyron/fhir/auth.py +282 -0
- fyron/fhir/rest.py +987 -0
- fyron/fhir/sql.py +478 -0
- fyron/fhir/types.py +40 -0
- fyron/fhir/utils.py +309 -0
- fyron/llm/__init__.py +5 -0
- fyron/llm/agent.py +649 -0
- fyron-0.1.0.dist-info/METADATA +576 -0
- fyron-0.1.0.dist-info/RECORD +21 -0
- fyron-0.1.0.dist-info/WHEEL +4 -0
- fyron-0.1.0.dist-info/entry_points.txt +3 -0
- fyron-0.1.0.dist-info/licenses/LICENSE +21 -0
fyron/__init__.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Fyron: interoperable healthcare data and AI workflows."""
|
|
2
|
+
|
|
3
|
+
from .fhir.auth import Auth
|
|
4
|
+
|
|
5
|
+
from .fhir.rest import FHIRRestClient
|
|
6
|
+
try:
|
|
7
|
+
from .fhir.sql import FHIRSQLClient
|
|
8
|
+
except Exception: # pragma: no cover
|
|
9
|
+
FHIRSQLClient = None # type: ignore
|
|
10
|
+
from .dicom.client import DICOMDownloader
|
|
11
|
+
from .llm.agent import LLMAgent
|
|
12
|
+
from .documents.client import DocumentDownloader
|
|
13
|
+
from .fhir.types import FHIRObj
|
|
14
|
+
from .fhir.utils import (
|
|
15
|
+
extract_condition,
|
|
16
|
+
extract_diagnostic_report,
|
|
17
|
+
extract_encounter,
|
|
18
|
+
extract_imaging_study,
|
|
19
|
+
extract_observation,
|
|
20
|
+
extract_patient,
|
|
21
|
+
extract_procedure,
|
|
22
|
+
process_condition_bundle,
|
|
23
|
+
process_diagnostic_report_bundle,
|
|
24
|
+
process_encounter_bundle,
|
|
25
|
+
process_imaging_study_bundle,
|
|
26
|
+
process_observation_bundle,
|
|
27
|
+
process_patient_bundle,
|
|
28
|
+
process_procedure_bundle,
|
|
29
|
+
safe_get,
|
|
30
|
+
)
|
|
31
|
+
from .core.io import DataIO, TeableClient, read_csv, read_excel, write_csv, write_excel
|
|
32
|
+
from .core.env import load_env
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Auth",
|
|
36
|
+
"FHIRRestClient",
|
|
37
|
+
"FHIRSQLClient",
|
|
38
|
+
"DICOMDownloader",
|
|
39
|
+
"LLMAgent",
|
|
40
|
+
"DocumentDownloader",
|
|
41
|
+
"FHIRObj",
|
|
42
|
+
"extract_condition",
|
|
43
|
+
"extract_diagnostic_report",
|
|
44
|
+
"extract_encounter",
|
|
45
|
+
"extract_imaging_study",
|
|
46
|
+
"extract_observation",
|
|
47
|
+
"extract_patient",
|
|
48
|
+
"extract_procedure",
|
|
49
|
+
"process_condition_bundle",
|
|
50
|
+
"process_diagnostic_report_bundle",
|
|
51
|
+
"process_encounter_bundle",
|
|
52
|
+
"process_imaging_study_bundle",
|
|
53
|
+
"process_observation_bundle",
|
|
54
|
+
"process_patient_bundle",
|
|
55
|
+
"process_procedure_bundle",
|
|
56
|
+
"safe_get",
|
|
57
|
+
"DataIO",
|
|
58
|
+
"TeableClient",
|
|
59
|
+
"load_env",
|
|
60
|
+
"read_csv",
|
|
61
|
+
"read_excel",
|
|
62
|
+
"write_csv",
|
|
63
|
+
"write_excel",
|
|
64
|
+
]
|
|
65
|
+
__version__ = "0.1.0"
|
fyron/core/__init__.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Core/shared utilities for Fyron."""
|
|
2
|
+
|
|
3
|
+
from .env import load_env
|
|
4
|
+
from .io import DataIO, TeableClient, read_csv, read_excel, write_csv, write_excel
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"DataIO",
|
|
8
|
+
"TeableClient",
|
|
9
|
+
"load_env",
|
|
10
|
+
"read_csv",
|
|
11
|
+
"read_excel",
|
|
12
|
+
"write_csv",
|
|
13
|
+
"write_excel",
|
|
14
|
+
]
|
fyron/core/env.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Environment loading helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from dotenv import find_dotenv, load_dotenv
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def load_env(
|
|
14
|
+
path: Optional[str] = None,
|
|
15
|
+
*,
|
|
16
|
+
override: bool = False,
|
|
17
|
+
warn_if_missing: bool = True,
|
|
18
|
+
) -> Optional[str]:
|
|
19
|
+
"""Load environment variables from a .env file.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
path:
|
|
24
|
+
Optional path to a .env file. If None, searches from the current working directory.
|
|
25
|
+
override:
|
|
26
|
+
Whether to override existing environment variables.
|
|
27
|
+
warn_if_missing:
|
|
28
|
+
If True, logs a warning when no .env file is found.
|
|
29
|
+
"""
|
|
30
|
+
env_path = path or find_dotenv(usecwd=True)
|
|
31
|
+
if env_path:
|
|
32
|
+
load_dotenv(env_path, override=override)
|
|
33
|
+
logger.info("Loaded environment from %s", env_path)
|
|
34
|
+
return env_path
|
|
35
|
+
if warn_if_missing:
|
|
36
|
+
logger.warning("No .env file found. Set environment variables or create a .env file.")
|
|
37
|
+
return None
|
fyron/core/io.py
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
"""IO helpers for CSV/Excel and Teable tables."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Dict, Iterable, List, Optional
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import requests
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DataIO:
|
|
17
|
+
"""Convenience wrappers for CSV/Excel IO."""
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def read_csv(path: str, **kwargs: Any) -> pd.DataFrame:
|
|
21
|
+
return pd.read_csv(path, **kwargs)
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def write_csv(df: pd.DataFrame, path: str, **kwargs: Any) -> None:
|
|
25
|
+
df.to_csv(path, index=False, **kwargs)
|
|
26
|
+
|
|
27
|
+
@staticmethod
|
|
28
|
+
def read_excel(path: str, **kwargs: Any) -> pd.DataFrame:
|
|
29
|
+
return pd.read_excel(path, **kwargs)
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def write_excel(df: pd.DataFrame, path: str, **kwargs: Any) -> None:
|
|
33
|
+
df.to_excel(path, index=False, **kwargs)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TeableConfig:
|
|
38
|
+
base_url: str
|
|
39
|
+
token: str
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TeableClient:
|
|
43
|
+
"""Minimal Teable table client for reading/writing DataFrames.
|
|
44
|
+
|
|
45
|
+
Environment variables:
|
|
46
|
+
- TEABLE_BASE_URL
|
|
47
|
+
- TEABLE_TOKEN
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
base_url: Optional[str] = None,
|
|
53
|
+
token: Optional[str] = None,
|
|
54
|
+
timeout: int = 30,
|
|
55
|
+
) -> None:
|
|
56
|
+
self.config = TeableConfig(
|
|
57
|
+
base_url=base_url or os.environ.get("TEABLE_BASE_URL", "https://app.teable.ai"),
|
|
58
|
+
token=token or os.environ.get("TEABLE_TOKEN", ""),
|
|
59
|
+
)
|
|
60
|
+
if not self.config.token:
|
|
61
|
+
raise ValueError("Teable token is required (set TEABLE_TOKEN or pass token).")
|
|
62
|
+
self.timeout = timeout
|
|
63
|
+
self.session = requests.Session()
|
|
64
|
+
self.session.headers.update(
|
|
65
|
+
{
|
|
66
|
+
"Authorization": f"Bearer {self.config.token}",
|
|
67
|
+
"Content-Type": "application/json",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def list_records(
|
|
72
|
+
self,
|
|
73
|
+
table_id: str,
|
|
74
|
+
*,
|
|
75
|
+
view_id: Optional[str] = None,
|
|
76
|
+
take: int = 1000,
|
|
77
|
+
skip: int = 0,
|
|
78
|
+
field_key_type: str = "name",
|
|
79
|
+
cell_format: str = "json",
|
|
80
|
+
projection: Optional[List[str]] = None,
|
|
81
|
+
**params: Any,
|
|
82
|
+
) -> Dict[str, Any]:
|
|
83
|
+
url = self._url(f"/api/table/{table_id}/record")
|
|
84
|
+
query: Dict[str, Any] = {
|
|
85
|
+
"take": take,
|
|
86
|
+
"skip": skip,
|
|
87
|
+
"fieldKeyType": field_key_type,
|
|
88
|
+
"cellFormat": cell_format,
|
|
89
|
+
}
|
|
90
|
+
if view_id:
|
|
91
|
+
query["viewId"] = view_id
|
|
92
|
+
if projection:
|
|
93
|
+
query["projection"] = projection
|
|
94
|
+
query.update(params)
|
|
95
|
+
resp = self.session.get(url, params=query, timeout=self.timeout)
|
|
96
|
+
resp.raise_for_status()
|
|
97
|
+
return resp.json()
|
|
98
|
+
|
|
99
|
+
def list_spaces(self) -> List[Dict[str, Any]]:
|
|
100
|
+
"""List spaces available to the current token."""
|
|
101
|
+
url = self._url("/api/space")
|
|
102
|
+
resp = self.session.get(url, timeout=self.timeout)
|
|
103
|
+
resp.raise_for_status()
|
|
104
|
+
return resp.json()
|
|
105
|
+
|
|
106
|
+
def list_bases(self, *, space_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
|
107
|
+
"""List bases for a space or all accessible bases."""
|
|
108
|
+
if space_id:
|
|
109
|
+
url = self._url(f"/api/space/{space_id}/base")
|
|
110
|
+
else:
|
|
111
|
+
url = self._url("/api/base/access/all")
|
|
112
|
+
resp = self.session.get(url, timeout=self.timeout)
|
|
113
|
+
resp.raise_for_status()
|
|
114
|
+
return resp.json()
|
|
115
|
+
|
|
116
|
+
def create_base(
|
|
117
|
+
self,
|
|
118
|
+
*,
|
|
119
|
+
space_id: str,
|
|
120
|
+
name: str,
|
|
121
|
+
icon: Optional[str] = None,
|
|
122
|
+
) -> Dict[str, Any]:
|
|
123
|
+
"""Create a base in a space."""
|
|
124
|
+
url = self._url("/api/base")
|
|
125
|
+
body: Dict[str, Any] = {"spaceId": space_id, "name": name}
|
|
126
|
+
if icon:
|
|
127
|
+
body["icon"] = icon
|
|
128
|
+
resp = self.session.post(url, json=body, timeout=self.timeout)
|
|
129
|
+
resp.raise_for_status()
|
|
130
|
+
return resp.json()
|
|
131
|
+
|
|
132
|
+
def get_or_create_base(
|
|
133
|
+
self,
|
|
134
|
+
*,
|
|
135
|
+
name: str,
|
|
136
|
+
space_id: Optional[str] = None,
|
|
137
|
+
space_name: Optional[str] = None,
|
|
138
|
+
icon: Optional[str] = None,
|
|
139
|
+
) -> Dict[str, Any]:
|
|
140
|
+
"""Return a base by name, creating it if it doesn't exist."""
|
|
141
|
+
resolved_space_id = space_id
|
|
142
|
+
if not resolved_space_id and space_name:
|
|
143
|
+
spaces = self.list_spaces()
|
|
144
|
+
matches = [s for s in spaces if s.get("name") == space_name]
|
|
145
|
+
if not matches:
|
|
146
|
+
raise ValueError(f"Space '{space_name}' not found.")
|
|
147
|
+
resolved_space_id = matches[0].get("id")
|
|
148
|
+
if not resolved_space_id:
|
|
149
|
+
spaces = self.list_spaces()
|
|
150
|
+
if len(spaces) == 1:
|
|
151
|
+
resolved_space_id = spaces[0].get("id")
|
|
152
|
+
else:
|
|
153
|
+
raise ValueError("space_id (or space_name) is required to create a base.")
|
|
154
|
+
|
|
155
|
+
bases = self.list_bases(space_id=resolved_space_id)
|
|
156
|
+
for base in bases:
|
|
157
|
+
if base.get("name") == name:
|
|
158
|
+
return base
|
|
159
|
+
return self.create_base(space_id=resolved_space_id, name=name, icon=icon)
|
|
160
|
+
|
|
161
|
+
def list_tables(self, base_id: str) -> List[Dict[str, Any]]:
|
|
162
|
+
"""List tables in a base."""
|
|
163
|
+
url = self._url(f"/api/base/{base_id}/table")
|
|
164
|
+
resp = self.session.get(url, timeout=self.timeout)
|
|
165
|
+
resp.raise_for_status()
|
|
166
|
+
return resp.json()
|
|
167
|
+
|
|
168
|
+
def create_table(
|
|
169
|
+
self,
|
|
170
|
+
*,
|
|
171
|
+
base_id: str,
|
|
172
|
+
name: str,
|
|
173
|
+
db_table_name: Optional[str] = None,
|
|
174
|
+
description: Optional[str] = None,
|
|
175
|
+
icon: Optional[str] = None,
|
|
176
|
+
fields: Optional[List[Dict[str, Any]]] = None,
|
|
177
|
+
views: Optional[List[Dict[str, Any]]] = None,
|
|
178
|
+
records: Optional[List[Dict[str, Any]]] = None,
|
|
179
|
+
order: Optional[int] = None,
|
|
180
|
+
field_key_type: str = "name",
|
|
181
|
+
) -> Dict[str, Any]:
|
|
182
|
+
"""Create a table in a base."""
|
|
183
|
+
url = self._url(f"/api/base/{base_id}/table")
|
|
184
|
+
body: Dict[str, Any] = {"name": name, "fieldKeyType": field_key_type}
|
|
185
|
+
if db_table_name:
|
|
186
|
+
body["dbTableName"] = db_table_name
|
|
187
|
+
if description:
|
|
188
|
+
body["description"] = description
|
|
189
|
+
if icon:
|
|
190
|
+
body["icon"] = icon
|
|
191
|
+
if fields is not None:
|
|
192
|
+
body["fields"] = fields
|
|
193
|
+
if views is not None:
|
|
194
|
+
body["views"] = views
|
|
195
|
+
if records is not None:
|
|
196
|
+
body["records"] = records
|
|
197
|
+
if order is not None:
|
|
198
|
+
body["order"] = order
|
|
199
|
+
resp = self.session.post(url, json=body, timeout=self.timeout)
|
|
200
|
+
resp.raise_for_status()
|
|
201
|
+
return resp.json()
|
|
202
|
+
|
|
203
|
+
def get_or_create_table(
|
|
204
|
+
self,
|
|
205
|
+
*,
|
|
206
|
+
base_id: str,
|
|
207
|
+
name: str,
|
|
208
|
+
db_table_name: Optional[str] = None,
|
|
209
|
+
description: Optional[str] = None,
|
|
210
|
+
icon: Optional[str] = None,
|
|
211
|
+
fields: Optional[List[Dict[str, Any]]] = None,
|
|
212
|
+
views: Optional[List[Dict[str, Any]]] = None,
|
|
213
|
+
records: Optional[List[Dict[str, Any]]] = None,
|
|
214
|
+
order: Optional[int] = None,
|
|
215
|
+
field_key_type: str = "name",
|
|
216
|
+
) -> Dict[str, Any]:
|
|
217
|
+
"""Return a table by name (or db_table_name), creating it if missing."""
|
|
218
|
+
tables = self.list_tables(base_id)
|
|
219
|
+
for table in tables:
|
|
220
|
+
if table.get("name") == name:
|
|
221
|
+
return table
|
|
222
|
+
if db_table_name and table.get("dbTableName") == db_table_name:
|
|
223
|
+
return table
|
|
224
|
+
return self.create_table(
|
|
225
|
+
base_id=base_id,
|
|
226
|
+
name=name,
|
|
227
|
+
db_table_name=db_table_name,
|
|
228
|
+
description=description,
|
|
229
|
+
icon=icon,
|
|
230
|
+
fields=fields,
|
|
231
|
+
views=views,
|
|
232
|
+
records=records,
|
|
233
|
+
order=order,
|
|
234
|
+
field_key_type=field_key_type,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def read_table(
|
|
238
|
+
self,
|
|
239
|
+
table_id: str,
|
|
240
|
+
*,
|
|
241
|
+
view_id: Optional[str] = None,
|
|
242
|
+
field_key_type: str = "name",
|
|
243
|
+
cell_format: str = "json",
|
|
244
|
+
projection: Optional[List[str]] = None,
|
|
245
|
+
take: int = 1000,
|
|
246
|
+
max_records: Optional[int] = None,
|
|
247
|
+
) -> pd.DataFrame:
|
|
248
|
+
records: List[Dict[str, Any]] = []
|
|
249
|
+
skip = 0
|
|
250
|
+
while True:
|
|
251
|
+
payload = self.list_records(
|
|
252
|
+
table_id,
|
|
253
|
+
view_id=view_id,
|
|
254
|
+
take=take,
|
|
255
|
+
skip=skip,
|
|
256
|
+
field_key_type=field_key_type,
|
|
257
|
+
cell_format=cell_format,
|
|
258
|
+
projection=projection,
|
|
259
|
+
)
|
|
260
|
+
batch = payload.get("records", [])
|
|
261
|
+
records.extend(batch)
|
|
262
|
+
if max_records is not None and len(records) >= max_records:
|
|
263
|
+
records = records[:max_records]
|
|
264
|
+
break
|
|
265
|
+
if len(batch) < take:
|
|
266
|
+
break
|
|
267
|
+
skip += take
|
|
268
|
+
rows = [record.get("fields", {}) for record in records]
|
|
269
|
+
df = pd.DataFrame(rows)
|
|
270
|
+
if "id" not in df.columns and records:
|
|
271
|
+
df.insert(0, "record_id", [r.get("id") for r in records])
|
|
272
|
+
return df
|
|
273
|
+
|
|
274
|
+
def create_records(
|
|
275
|
+
self,
|
|
276
|
+
table_id: str,
|
|
277
|
+
records: Iterable[Dict[str, Any]],
|
|
278
|
+
*,
|
|
279
|
+
field_key_type: str = "name",
|
|
280
|
+
typecast: bool = True,
|
|
281
|
+
order: Optional[Dict[str, Any]] = None,
|
|
282
|
+
) -> Dict[str, Any]:
|
|
283
|
+
url = self._url(f"/api/table/{table_id}/record")
|
|
284
|
+
body: Dict[str, Any] = {
|
|
285
|
+
"records": [{"fields": r} for r in records],
|
|
286
|
+
"fieldKeyType": field_key_type,
|
|
287
|
+
"typecast": typecast,
|
|
288
|
+
}
|
|
289
|
+
if order:
|
|
290
|
+
body["order"] = order
|
|
291
|
+
resp = self.session.post(url, json=body, timeout=self.timeout)
|
|
292
|
+
resp.raise_for_status()
|
|
293
|
+
return resp.json()
|
|
294
|
+
|
|
295
|
+
def delete_records(
|
|
296
|
+
self,
|
|
297
|
+
table_id: str,
|
|
298
|
+
record_ids: List[str],
|
|
299
|
+
) -> None:
|
|
300
|
+
"""Delete records by ID."""
|
|
301
|
+
if not record_ids:
|
|
302
|
+
return
|
|
303
|
+
url = self._url(f"/api/table/{table_id}/record")
|
|
304
|
+
resp = self.session.delete(url, params={"recordIds": record_ids}, timeout=self.timeout)
|
|
305
|
+
resp.raise_for_status()
|
|
306
|
+
|
|
307
|
+
def clear_table(
|
|
308
|
+
self,
|
|
309
|
+
table_id: str,
|
|
310
|
+
*,
|
|
311
|
+
batch_size: int = 200,
|
|
312
|
+
take: int = 1000,
|
|
313
|
+
) -> int:
|
|
314
|
+
"""Delete all records in a table and return deleted count."""
|
|
315
|
+
deleted = 0
|
|
316
|
+
skip = 0
|
|
317
|
+
while True:
|
|
318
|
+
payload = self.list_records(table_id, take=take, skip=skip)
|
|
319
|
+
records = payload.get("records", [])
|
|
320
|
+
record_ids = [r.get("id") for r in records if r.get("id")]
|
|
321
|
+
if not record_ids:
|
|
322
|
+
break
|
|
323
|
+
for idx in range(0, len(record_ids), batch_size):
|
|
324
|
+
self.delete_records(table_id, record_ids[idx : idx + batch_size])
|
|
325
|
+
deleted += len(record_ids[idx : idx + batch_size])
|
|
326
|
+
if len(records) < take:
|
|
327
|
+
break
|
|
328
|
+
skip += take
|
|
329
|
+
return deleted
|
|
330
|
+
|
|
331
|
+
def write_dataframe(
|
|
332
|
+
self,
|
|
333
|
+
table_id: str,
|
|
334
|
+
df: pd.DataFrame,
|
|
335
|
+
*,
|
|
336
|
+
field_key_type: str = "name",
|
|
337
|
+
typecast: bool = True,
|
|
338
|
+
chunk_size: int = 200,
|
|
339
|
+
) -> List[str]:
|
|
340
|
+
record_ids: List[str] = []
|
|
341
|
+
rows = df.to_dict(orient="records")
|
|
342
|
+
for idx in range(0, len(rows), chunk_size):
|
|
343
|
+
batch = rows[idx : idx + chunk_size]
|
|
344
|
+
payload = self.create_records(
|
|
345
|
+
table_id,
|
|
346
|
+
batch,
|
|
347
|
+
field_key_type=field_key_type,
|
|
348
|
+
typecast=typecast,
|
|
349
|
+
)
|
|
350
|
+
record_ids.extend([r.get("id") for r in payload.get("records", [])])
|
|
351
|
+
return record_ids
|
|
352
|
+
|
|
353
|
+
def overwrite_table(
|
|
354
|
+
self,
|
|
355
|
+
table_id: str,
|
|
356
|
+
df: pd.DataFrame,
|
|
357
|
+
*,
|
|
358
|
+
delete_batch_size: int = 200,
|
|
359
|
+
create_chunk_size: int = 200,
|
|
360
|
+
field_key_type: str = "name",
|
|
361
|
+
typecast: bool = True,
|
|
362
|
+
) -> List[str]:
|
|
363
|
+
"""Delete all records in a table, then write the DataFrame."""
|
|
364
|
+
self.clear_table(table_id, batch_size=delete_batch_size)
|
|
365
|
+
return self.write_dataframe(
|
|
366
|
+
table_id,
|
|
367
|
+
df,
|
|
368
|
+
field_key_type=field_key_type,
|
|
369
|
+
typecast=typecast,
|
|
370
|
+
chunk_size=create_chunk_size,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def _url(self, path: str) -> str:
|
|
374
|
+
return self.config.base_url.rstrip("/") + path
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def read_csv(path: str, **kwargs: Any) -> pd.DataFrame:
|
|
378
|
+
"""Read a CSV file (shortcut for DataIO.read_csv)."""
|
|
379
|
+
return DataIO.read_csv(path, **kwargs)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def write_csv(df: pd.DataFrame, path: str, **kwargs: Any) -> None:
|
|
383
|
+
"""Write a DataFrame to CSV (shortcut for DataIO.write_csv)."""
|
|
384
|
+
DataIO.write_csv(df, path, **kwargs)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def read_excel(path: str, **kwargs: Any) -> pd.DataFrame:
|
|
388
|
+
"""Read an Excel file (shortcut for DataIO.read_excel)."""
|
|
389
|
+
return DataIO.read_excel(path, **kwargs)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def write_excel(df: pd.DataFrame, path: str, **kwargs: Any) -> None:
|
|
393
|
+
"""Write a DataFrame to Excel (shortcut for DataIO.write_excel)."""
|
|
394
|
+
DataIO.write_excel(df, path, **kwargs)
|