tablemaster 2.1.3__tar.gz → 2.1.5__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.
- {tablemaster-2.1.3 → tablemaster-2.1.5}/PKG-INFO +2 -2
- {tablemaster-2.1.3 → tablemaster-2.1.5}/pyproject.toml +2 -2
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/database.py +1 -1
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/feishu.py +2 -2
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/gspread.py +85 -4
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster.egg-info/PKG-INFO +2 -2
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster.egg-info/requires.txt +1 -1
- {tablemaster-2.1.3 → tablemaster-2.1.5}/LICENSE +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/README.md +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/setup.cfg +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/__init__.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/__main__.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/cli.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/config.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/local.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/__init__.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/apply.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/dialects/__init__.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/dialects/base.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/dialects/mysql.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/dialects/postgresql.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/dialects/tidb.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/diff.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/init.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/introspect.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/loader.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/models.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/plan.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/schema/pull.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/sync.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster/utils.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster.egg-info/SOURCES.txt +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster.egg-info/dependency_links.txt +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster.egg-info/entry_points.txt +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tablemaster.egg-info/top_level.txt +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tests/test_error_visibility.py +0 -0
- {tablemaster-2.1.3 → tablemaster-2.1.5}/tests/test_schema_core.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tablemaster
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.5
|
|
4
4
|
Summary: tablemaster is a Python toolkit for moving and managing tabular data across databases, Feishu/Lark, Google Sheets, and local files with one consistent API.
|
|
5
5
|
Author-email: Livid <livid.su@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ilivid/tablemaster
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: pandas>=1.5
|
|
11
11
|
Requires-Dist: pyyaml>=6
|
|
12
12
|
Requires-Dist: python-dateutil>=2.8
|
|
13
13
|
Requires-Dist: tqdm>=4.60
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "tablemaster"
|
|
7
|
-
version = "2.1.
|
|
7
|
+
version = "2.1.5"
|
|
8
8
|
description = "tablemaster is a Python toolkit for moving and managing tabular data across databases, Feishu/Lark, Google Sheets, and local files with one consistent API."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -12,7 +12,7 @@ authors = [
|
|
|
12
12
|
{ name = "Livid", email = "livid.su@gmail.com" }
|
|
13
13
|
]
|
|
14
14
|
dependencies = [
|
|
15
|
-
"pandas>=1.5
|
|
15
|
+
"pandas>=1.5",
|
|
16
16
|
"pyyaml>=6",
|
|
17
17
|
"python-dateutil>=2.8",
|
|
18
18
|
"tqdm>=4.60",
|
|
@@ -409,7 +409,7 @@ class ManageTable:
|
|
|
409
409
|
VALUES ({value_placeholders})
|
|
410
410
|
"""
|
|
411
411
|
|
|
412
|
-
data = chunk.where(pd.notna(chunk), None).to_dict(orient='records')
|
|
412
|
+
data = chunk.astype(object).where(pd.notna(chunk), None).to_dict(orient='records')
|
|
413
413
|
connection.execute(text(insert_sql), data)
|
|
414
414
|
pbar.update(1)
|
|
415
415
|
except Exception as e:
|
|
@@ -192,7 +192,7 @@ def fs_write_df(sheet_address, df, feishu_cfg, loc='A1', clear_sheet=True):
|
|
|
192
192
|
df_copy[col] = df_copy[col].astype(str)
|
|
193
193
|
|
|
194
194
|
# 处理 NaN 值,转换为空字符串
|
|
195
|
-
df_copy = df_copy.fillna('')
|
|
195
|
+
df_copy = df_copy.astype(object).fillna('')
|
|
196
196
|
|
|
197
197
|
# 替换 'nan' 字符串为空字符串
|
|
198
198
|
df_copy = df_copy.replace('nan', '')
|
|
@@ -365,7 +365,7 @@ def fs_write_base(sheet_address, df, feishu_cfg, clear_table=False):
|
|
|
365
365
|
|
|
366
366
|
# 处理 DataFrame - 只保留有效字段
|
|
367
367
|
df_copy = df[list(valid_fields)].copy()
|
|
368
|
-
df_copy = df_copy.fillna('')
|
|
368
|
+
df_copy = df_copy.astype(object).fillna('')
|
|
369
369
|
df_copy = df_copy.replace('nan', '')
|
|
370
370
|
|
|
371
371
|
# 将 DataFrame 转换为 records 格式
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import gspread
|
|
2
|
+
import json
|
|
3
|
+
import math
|
|
2
4
|
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
3
6
|
import re
|
|
4
7
|
import warnings
|
|
5
8
|
import logging
|
|
9
|
+
from datetime import date, datetime, time
|
|
6
10
|
from functools import lru_cache
|
|
7
11
|
|
|
8
12
|
logger = logging.getLogger(__name__)
|
|
@@ -19,6 +23,85 @@ def _warn_deprecated(message):
|
|
|
19
23
|
warnings.warn(f'{message} This usage will be removed in a future release.', FutureWarning, stacklevel=3)
|
|
20
24
|
|
|
21
25
|
|
|
26
|
+
def _json_safe(value):
|
|
27
|
+
if value is None or value is pd.NA or value is pd.NaT:
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
if isinstance(value, pd.Timestamp):
|
|
31
|
+
return None if pd.isna(value) else value.isoformat()
|
|
32
|
+
if isinstance(value, pd.Timedelta):
|
|
33
|
+
return None if pd.isna(value) else str(value)
|
|
34
|
+
if isinstance(value, np.datetime64):
|
|
35
|
+
return None if np.isnat(value) else pd.Timestamp(value).isoformat()
|
|
36
|
+
if isinstance(value, np.timedelta64):
|
|
37
|
+
try:
|
|
38
|
+
if np.isnat(value):
|
|
39
|
+
return None
|
|
40
|
+
except TypeError:
|
|
41
|
+
pass
|
|
42
|
+
return str(pd.Timedelta(value))
|
|
43
|
+
if isinstance(value, (datetime, date, time)):
|
|
44
|
+
return value.isoformat()
|
|
45
|
+
|
|
46
|
+
if isinstance(value, (float, np.floating)):
|
|
47
|
+
if np.isnan(value) or np.isinf(value):
|
|
48
|
+
return None
|
|
49
|
+
return float(value)
|
|
50
|
+
if isinstance(value, np.integer):
|
|
51
|
+
return int(value)
|
|
52
|
+
if isinstance(value, np.bool_):
|
|
53
|
+
return bool(value)
|
|
54
|
+
|
|
55
|
+
if isinstance(value, dict):
|
|
56
|
+
return {k: _json_safe(v) for k, v in value.items()}
|
|
57
|
+
if isinstance(value, (list, tuple, set)):
|
|
58
|
+
return [_json_safe(v) for v in value]
|
|
59
|
+
if isinstance(value, np.ndarray):
|
|
60
|
+
return [_json_safe(v) for v in value.tolist()]
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
if math.isnan(value) or math.isinf(value):
|
|
64
|
+
return None
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
return value
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _build_values(df):
|
|
72
|
+
safe_cols = [_json_safe(col) for col in df.columns.tolist()]
|
|
73
|
+
safe_cols = [("" if col is None else col) for col in safe_cols]
|
|
74
|
+
for col_index, col_value in enumerate(safe_cols, start=1):
|
|
75
|
+
try:
|
|
76
|
+
json.dumps(col_value, allow_nan=False)
|
|
77
|
+
except (TypeError, ValueError) as exc:
|
|
78
|
+
raise ValueError(f'column header at col {col_index} is not JSON serializable: {exc}') from exc
|
|
79
|
+
|
|
80
|
+
rows = df.values.tolist()
|
|
81
|
+
safe_rows = []
|
|
82
|
+
for row_index, row in enumerate(rows, start=2):
|
|
83
|
+
safe_row = []
|
|
84
|
+
for col_index, cell in enumerate(row, start=1):
|
|
85
|
+
safe_cell = _json_safe(cell)
|
|
86
|
+
safe_cell = '' if safe_cell is None else safe_cell
|
|
87
|
+
try:
|
|
88
|
+
json.dumps(safe_cell, allow_nan=False)
|
|
89
|
+
except (TypeError, ValueError) as exc:
|
|
90
|
+
column_name = str(df.columns[col_index - 1])
|
|
91
|
+
raise ValueError(
|
|
92
|
+
f'cell at row {row_index}, col {col_index} ({column_name}) is not JSON serializable: {exc}'
|
|
93
|
+
) from exc
|
|
94
|
+
safe_row.append(safe_cell)
|
|
95
|
+
safe_rows.append(safe_row)
|
|
96
|
+
|
|
97
|
+
values = [safe_cols] + safe_rows
|
|
98
|
+
try:
|
|
99
|
+
json.dumps({'values': values}, allow_nan=False)
|
|
100
|
+
except (TypeError, ValueError) as exc:
|
|
101
|
+
raise ValueError(f'worksheet payload contains non-JSON-safe data: {exc}') from exc
|
|
102
|
+
return values
|
|
103
|
+
|
|
104
|
+
|
|
22
105
|
def _resolve_service_account_path(cfg, service_account_path):
|
|
23
106
|
if service_account_path:
|
|
24
107
|
_warn_deprecated('service_account_path argument is deprecated; pass a cfg object instead.')
|
|
@@ -123,10 +206,8 @@ def gs_write_df(address, df, cfg=None, loc='A1', service_account_path=None):
|
|
|
123
206
|
try:
|
|
124
207
|
wks.clear()
|
|
125
208
|
df_copy = df.copy()
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
df_copy[col] = df_copy[col].astype(str)
|
|
129
|
-
wks.update(loc, ([df_copy.columns.values.tolist()] + df_copy.values.tolist()))
|
|
209
|
+
values = _build_values(df_copy)
|
|
210
|
+
wks.update(loc, values)
|
|
130
211
|
|
|
131
212
|
logger.info('data is written')
|
|
132
213
|
except Exception as e:
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tablemaster
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.5
|
|
4
4
|
Summary: tablemaster is a Python toolkit for moving and managing tabular data across databases, Feishu/Lark, Google Sheets, and local files with one consistent API.
|
|
5
5
|
Author-email: Livid <livid.su@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/ilivid/tablemaster
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Requires-Dist: pandas
|
|
10
|
+
Requires-Dist: pandas>=1.5
|
|
11
11
|
Requires-Dist: pyyaml>=6
|
|
12
12
|
Requires-Dist: python-dateutil>=2.8
|
|
13
13
|
Requires-Dist: tqdm>=4.60
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|