tpltable 0.3.2__tar.gz → 0.3.4__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.
- {tpltable-0.3.2 → tpltable-0.3.4}/PKG-INFO +1 -1
- {tpltable-0.3.2 → tpltable-0.3.4}/setup.py +1 -1
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/basic.py +9 -3
- tpltable-0.3.4/tpltable/core.py +12 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/excel.py +1 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/style.py +26 -5
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/table.py +269 -87
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable.egg-info/PKG-INFO +1 -1
- tpltable-0.3.2/tpltable/core.py +0 -1
- {tpltable-0.3.2 → tpltable-0.3.4}/setup.cfg +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/__init__.py +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/main.py +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable/style_demo.py +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable.egg-info/SOURCES.txt +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable.egg-info/dependency_links.txt +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable.egg-info/requires.txt +0 -0
- {tpltable-0.3.2 → tpltable-0.3.4}/tpltable.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tpltable
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)
|
|
5
5
|
Author-email: 2229066748@qq.com
|
|
6
6
|
Maintainer: Eagle'sBaby
|
|
@@ -5,7 +5,7 @@ from setuptools import find_packages, setup
|
|
|
5
5
|
|
|
6
6
|
setup(
|
|
7
7
|
name='tpltable',
|
|
8
|
-
version='0.3.
|
|
8
|
+
version='0.3.4',
|
|
9
9
|
description='define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)',
|
|
10
10
|
author_email='2229066748@qq.com',
|
|
11
11
|
maintainer="Eagle'sBaby",
|
|
@@ -19,7 +19,8 @@ from openpyxl.styles import Font, Color, Alignment, Border, Side, PatternFill
|
|
|
19
19
|
NaN = pd.NA
|
|
20
20
|
nan = pd.NA
|
|
21
21
|
|
|
22
|
-
isnan
|
|
22
|
+
def isnan(value):
|
|
23
|
+
return pd.isna(value) or str(value) == 'nan'
|
|
23
24
|
|
|
24
25
|
# NOTE: Colorama Start -------------------------------------------------------------------------->
|
|
25
26
|
# 初始化colorama
|
|
@@ -72,6 +73,11 @@ COLORAMA_STYLES = {
|
|
|
72
73
|
|
|
73
74
|
__PAT_COORD = re.compile("([A-Z]+)(\d+)")
|
|
74
75
|
|
|
76
|
+
|
|
77
|
+
# 模糊的行列索引混用警告
|
|
78
|
+
class AmbiguousIndexWarning(UserWarning):
|
|
79
|
+
...
|
|
80
|
+
|
|
75
81
|
class Coord:
|
|
76
82
|
def __init__(self, iloc_c, iloc_r):
|
|
77
83
|
self._r:int = iloc_r
|
|
@@ -142,7 +148,7 @@ def index_from_string(letters:str, *, only_int:bool=False, should:str=None) -> U
|
|
|
142
148
|
if letters.isdigit():
|
|
143
149
|
if should == 's':
|
|
144
150
|
warnings.warn(
|
|
145
|
-
f"Here should be a letter, but got '{letters}'. Regarded as '{get_column_letter(int(letters))}'."
|
|
151
|
+
AmbiguousIndexWarning(f"Here should be a letter (Col-Index), but got '{letters}' (Row-Index). Regarded as '{get_column_letter(int(letters))}'."),
|
|
146
152
|
)
|
|
147
153
|
return int(letters)
|
|
148
154
|
_s = __PAT_COORD.match(letters)
|
|
@@ -153,7 +159,7 @@ def index_from_string(letters:str, *, only_int:bool=False, should:str=None) -> U
|
|
|
153
159
|
return Coord(index_from_string(_c) - 1, int(_r) - 1)
|
|
154
160
|
if should == 'i':
|
|
155
161
|
warnings.warn(
|
|
156
|
-
f"Here should be an digit, but got '{letters}'. Regarded as '{_column_index_from_string(letters)}'."
|
|
162
|
+
AmbiguousIndexWarning(f"Here should be an digit (Row-Index), but got '{letters}' (Col-Index). Regarded as '{_column_index_from_string(letters)}'.")
|
|
157
163
|
)
|
|
158
164
|
return _column_index_from_string(letters)
|
|
159
165
|
|
|
@@ -77,6 +77,24 @@ COLOR_MAGENTA = '00FF00FF'
|
|
|
77
77
|
COLOR_DARKMAGENTA = '00800080'
|
|
78
78
|
COLOR_LIGHTMAGENTA = '00FFC0CB'
|
|
79
79
|
|
|
80
|
+
SIDE_THIN_BLACK = Side(style='thin', color=COLOR_BLACK)
|
|
81
|
+
|
|
82
|
+
BORDER_LTRB = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
|
|
83
|
+
BORDER_NOLEFT = Border(None, SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
|
|
84
|
+
BORDER_NORIGHT = Border(SIDE_THIN_BLACK, None, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
|
|
85
|
+
BORDER_NOTOP = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, None, SIDE_THIN_BLACK)
|
|
86
|
+
BORDER_NOBOTTOM = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, SIDE_THIN_BLACK, None)
|
|
87
|
+
BORDER_LT = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, None, None)
|
|
88
|
+
BORDER_RB = Border(None, None, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
|
|
89
|
+
BORDER_LB = Border(SIDE_THIN_BLACK, None, None, SIDE_THIN_BLACK)
|
|
90
|
+
BORDER_RT = Border(None, SIDE_THIN_BLACK, SIDE_THIN_BLACK, None)
|
|
91
|
+
BORDER_LR = Border(SIDE_THIN_BLACK, SIDE_THIN_BLACK, None, None)
|
|
92
|
+
BORDER_TB = Border(None, None, SIDE_THIN_BLACK, SIDE_THIN_BLACK)
|
|
93
|
+
BORDER_L = Border(SIDE_THIN_BLACK, None, None, None)
|
|
94
|
+
BORDER_R = Border(None, SIDE_THIN_BLACK, None, None)
|
|
95
|
+
BORDER_T = Border(None, None, SIDE_THIN_BLACK, None)
|
|
96
|
+
BORDER_B = Border(None, None, None, SIDE_THIN_BLACK)
|
|
97
|
+
|
|
80
98
|
|
|
81
99
|
def _GetCellStyle(ws, cell, style_type: str) -> object:
|
|
82
100
|
if style_type == TYPE_COLOR:
|
|
@@ -241,11 +259,11 @@ class Style:
|
|
|
241
259
|
# border_style: Incomplete | None = None,
|
|
242
260
|
_l, _r, _t, _b, _diag = _bd.left, _bd.right, _bd.top, _bd.bottom, _bd.diagonal
|
|
243
261
|
return {
|
|
244
|
-
'left': {'style': _l.style, 'color': _l.color, 'border_style': _l.border_style},
|
|
245
|
-
'right': {'style': _r.style, 'color': _r.color, 'border_style': _r.border_style},
|
|
246
|
-
'top': {'style': _t.style, 'color': _t.color, 'border_style': _t.border_style},
|
|
247
|
-
'bottom': {'style': _b.style, 'color': _b.color, 'border_style': _b.border_style},
|
|
248
|
-
'diagonal': {'style': _diag.style, 'color': _diag.color, 'border_style': _diag.border_style},
|
|
262
|
+
'left': {'style': _l.style, 'color': _l.color, 'border_style': _l.border_style} if _l is not None else None,
|
|
263
|
+
'right': {'style': _r.style, 'color': _r.color, 'border_style': _r.border_style} if _r is not None else None,
|
|
264
|
+
'top': {'style': _t.style, 'color': _t.color, 'border_style': _t.border_style} if _t is not None else None,
|
|
265
|
+
'bottom': {'style': _b.style, 'color': _b.color, 'border_style': _b.border_style} if _b is not None else None,
|
|
266
|
+
'diagonal': {'style': _diag.style, 'color': _diag.color, 'border_style': _diag.border_style} if _diag is not None else None,
|
|
249
267
|
'diagonal_direction': _bd.diagonal_direction,
|
|
250
268
|
'vertical': _bd.vertical, 'horizontal': _bd.horizontal,
|
|
251
269
|
'diagonalUp': _bd.diagonalUp, 'diagonalDown': _bd.diagonalDown,
|
|
@@ -301,6 +319,9 @@ class Style:
|
|
|
301
319
|
self[key] = value
|
|
302
320
|
|
|
303
321
|
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
|
|
304
325
|
def _raw_style() -> Style:
|
|
305
326
|
wb = Workbook()
|
|
306
327
|
ws = wb.active
|
|
@@ -4,13 +4,99 @@ import warnings
|
|
|
4
4
|
import numpy as np
|
|
5
5
|
import pandas as pd
|
|
6
6
|
from tpltable.core import *
|
|
7
|
-
from tpltable.style import Style
|
|
7
|
+
from tpltable.style import Style, TYPE_BORDER
|
|
8
8
|
|
|
9
9
|
_PAT_COORD = re.compile(r"([A-Z]+)(\d+)")
|
|
10
|
+
|
|
11
|
+
|
|
10
12
|
class EMPTY_INDEX:
|
|
11
13
|
pass
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
class _ExcelIndexsLike_1DArray_Def:
|
|
17
|
+
|
|
18
|
+
def _reindexself(self) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Reindex the Series
|
|
21
|
+
* Return None. Modify self._sr inplace.
|
|
22
|
+
:return:
|
|
23
|
+
"""
|
|
24
|
+
raise NotImplementedError
|
|
25
|
+
|
|
26
|
+
def _reconstruct(self, other) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Reconstruct self
|
|
29
|
+
* Return None. Modify self._sr inplace.
|
|
30
|
+
:param other: inst of __class__. The other instance.
|
|
31
|
+
:return:
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
def _construct_new_from_index(self, new_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
|
|
36
|
+
"""
|
|
37
|
+
Construct a new __class__ from the new index
|
|
38
|
+
* Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
|
|
39
|
+
:param new_index: int|slice. The new index you want to index self
|
|
40
|
+
*
|
|
41
|
+
:param inplace: bool. If True, modify self inplace. If True, Return None.
|
|
42
|
+
:return: inst of __class__ | None
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError
|
|
45
|
+
|
|
46
|
+
def _construct_new_from_insert(self, insert_index: int, *values, inplace=False) -> '__class__() | None':
|
|
47
|
+
"""
|
|
48
|
+
Construct a new __class__ from the new index
|
|
49
|
+
* Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
|
|
50
|
+
:param insert_index: int. The index to insert before.
|
|
51
|
+
:param *values: Any. The values to insert.
|
|
52
|
+
:param inplace:
|
|
53
|
+
:return: inst of __class__ | None
|
|
54
|
+
"""
|
|
55
|
+
raise NotImplementedError
|
|
56
|
+
|
|
57
|
+
def _construct_new_from_delete(self, delete_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
|
|
58
|
+
"""
|
|
59
|
+
Construct a new __class__ from the new index
|
|
60
|
+
* Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
|
|
61
|
+
:param delete_index: int|slice. The index to delete.
|
|
62
|
+
*
|
|
63
|
+
:param inplace: bool. If True, modify self inplace. If True, Return None.
|
|
64
|
+
:return: inst of __class__ | None
|
|
65
|
+
"""
|
|
66
|
+
raise NotImplementedError
|
|
67
|
+
|
|
68
|
+
def on_subinstance(self, construction: object) -> '__class__()':
|
|
69
|
+
"""
|
|
70
|
+
Hook function called when a subinstance is created.
|
|
71
|
+
* Return the new instance. So you can modify the new instance.
|
|
72
|
+
* When this method is called, self is not modified.
|
|
73
|
+
:param construction: inst of __class__. The subinstance created.
|
|
74
|
+
:return: inst of __class__ | None
|
|
75
|
+
"""
|
|
76
|
+
return construction
|
|
77
|
+
|
|
78
|
+
def on_insert(self, construction: object) -> '__class__()':
|
|
79
|
+
"""
|
|
80
|
+
Hook function called when a subinstance is created.
|
|
81
|
+
* Return the new instance. So you can modify the new instance.
|
|
82
|
+
* When this method is called, self is not modified.
|
|
83
|
+
:param construction: inst of __class__. The subinstance created.
|
|
84
|
+
:return: inst of __class__
|
|
85
|
+
"""
|
|
86
|
+
return construction
|
|
87
|
+
|
|
88
|
+
def on_delete(self, construction: object) -> '__class__()':
|
|
89
|
+
"""
|
|
90
|
+
Hook function called when a subinstance is created.
|
|
91
|
+
* Return the new instance. So you can modify the new instance.
|
|
92
|
+
* When this method is called, self is not modified.
|
|
93
|
+
:param construction: inst of __class__. The subinstance created.
|
|
94
|
+
:return: inst of __class__
|
|
95
|
+
"""
|
|
96
|
+
return construction
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _ExcelIndexsLike_1DArray_Base(_ExcelIndexsLike_1DArray_Def):
|
|
14
100
|
_CLS_DEFAULT_INDEX_TYPE = None # "i" or "s" or None
|
|
15
101
|
|
|
16
102
|
# None: integer index, start with 0 and label'0'
|
|
@@ -18,37 +104,119 @@ class _ExcelIndexsLike_1DArray:
|
|
|
18
104
|
# "s": letter index, start with 0 and label:'A'
|
|
19
105
|
def __init__(self, _from=None, *, copy: bool = True):
|
|
20
106
|
self._sr: pd.Series = None
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
107
|
+
|
|
108
|
+
if isinstance(_from, list):
|
|
109
|
+
try:
|
|
110
|
+
_from = np.array(_from)
|
|
111
|
+
except Exception as e:
|
|
112
|
+
raise ValueError(f"Failed to convert list to 1D array. {e}")
|
|
113
|
+
|
|
114
|
+
if _from is None:
|
|
115
|
+
self._sr = pd.Series()
|
|
25
116
|
elif isinstance(_from, np.ndarray):
|
|
26
117
|
# check shape
|
|
27
118
|
s = _from.shape
|
|
28
119
|
if np.ndim(_from) != 1:
|
|
29
120
|
raise ValueError(f"Only 1D array is supported. But got {np.ndim(_from)}D array.(shape={s})")
|
|
30
121
|
self._sr = pd.Series(_from)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
raise ValueError(f"Only 1D array is supported. But got {np.ndim(arr)}D array from list.(shape={s})")
|
|
38
|
-
self._sr = pd.Series(arr)
|
|
39
|
-
except Exception as e:
|
|
40
|
-
raise ValueError(f"Failed to convert list to 1D array. {e}")
|
|
122
|
+
elif isinstance(_from, pd.Series):
|
|
123
|
+
self._sr = _from
|
|
124
|
+
if copy:
|
|
125
|
+
self._sr = self._sr.copy()
|
|
126
|
+
elif isinstance(_from, _ExcelIndexsLike_1DArray_Base):
|
|
127
|
+
self._sr = _from._sr.copy()
|
|
41
128
|
else:
|
|
42
129
|
raise ValueError(f"Unsupported type: {type(_from)}")
|
|
43
130
|
|
|
44
|
-
self.
|
|
131
|
+
self._reindexself()
|
|
45
132
|
|
|
46
|
-
def
|
|
133
|
+
def _reindexself(self):
|
|
134
|
+
"""
|
|
135
|
+
Reindex the Series
|
|
136
|
+
* Return None. Modify self._sr inplace.
|
|
137
|
+
:return:
|
|
138
|
+
"""
|
|
47
139
|
if self._CLS_DEFAULT_INDEX_TYPE == "s":
|
|
48
140
|
self._sr.index = [get_column_letter(i + 1) for i in range(self.size)]
|
|
49
141
|
elif self._CLS_DEFAULT_INDEX_TYPE == "i":
|
|
50
142
|
self._sr.index = [i + 1 for i in range(self.size)]
|
|
51
143
|
|
|
144
|
+
def _reconstruct(self, other):
|
|
145
|
+
"""
|
|
146
|
+
Reconstruct self
|
|
147
|
+
* Return None. Modify self._sr inplace.
|
|
148
|
+
:param other: inst of __class__. The other instance.
|
|
149
|
+
:return:
|
|
150
|
+
"""
|
|
151
|
+
self._sr = other._sr.copy()
|
|
152
|
+
self._reindexself()
|
|
153
|
+
|
|
154
|
+
def _construct_new_from_index(self, new_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
|
|
155
|
+
"""
|
|
156
|
+
Construct a new __class__ from the new index
|
|
157
|
+
* Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
|
|
158
|
+
:param new_index: int|slice. The new index you want to index self
|
|
159
|
+
*
|
|
160
|
+
:param inplace: bool. If True, modify self inplace. If True, Return None.
|
|
161
|
+
:return: inst of __class__ | None
|
|
162
|
+
"""
|
|
163
|
+
_new = self.__class__(self._sr.iloc[new_index], copy=False)
|
|
164
|
+
_new = self.on_subinstance(_new)
|
|
165
|
+
if inplace:
|
|
166
|
+
self._reconstruct(_new)
|
|
167
|
+
else:
|
|
168
|
+
return _new
|
|
169
|
+
|
|
170
|
+
def _construct_new_from_insert(self, insert_index: int, *values, inplace=False) -> '__class__() | None':
|
|
171
|
+
"""
|
|
172
|
+
Construct a new __class__ from the new index
|
|
173
|
+
* Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
|
|
174
|
+
:param insert_index: int. The index to insert before.
|
|
175
|
+
:param *values: Any. The values to insert.
|
|
176
|
+
:param inplace:
|
|
177
|
+
:return: inst of __class__ | None
|
|
178
|
+
"""
|
|
179
|
+
_construct = self._sr.to_list()
|
|
180
|
+
values = list(values)
|
|
181
|
+
_construct = _construct[:insert_index] + values + _construct[insert_index:]
|
|
182
|
+
_new = self.__class__(_construct, copy=False)
|
|
183
|
+
_new = self.on_insert(_new)
|
|
184
|
+
if inplace:
|
|
185
|
+
self._reconstruct(_new)
|
|
186
|
+
else:
|
|
187
|
+
return _new
|
|
188
|
+
|
|
189
|
+
def _construct_new_from_delete(self, delete_index: Union[int, slice], *, inplace=False) -> '__class__() | None':
|
|
190
|
+
"""
|
|
191
|
+
Construct a new __class__ from the new index
|
|
192
|
+
* Use this instead of directly create a new __class__ object. So you can override this method to add some other logic.
|
|
193
|
+
:param delete_index: int|slice. The index to delete. Special case: slice(None) means clear all.
|
|
194
|
+
*
|
|
195
|
+
:param inplace: bool. If True, modify self inplace. If True, Return None.
|
|
196
|
+
:return: inst of __class__ | None
|
|
197
|
+
"""
|
|
198
|
+
if delete_index == slice(None):
|
|
199
|
+
_new = self.__class__()
|
|
200
|
+
else:
|
|
201
|
+
_new = self.__class__(self._sr.drop(self._sr.index[delete_index]), copy=False)
|
|
202
|
+
_new = self.on_delete(_new)
|
|
203
|
+
|
|
204
|
+
if inplace:
|
|
205
|
+
self._reconstruct(_new)
|
|
206
|
+
else:
|
|
207
|
+
return _new
|
|
208
|
+
|
|
209
|
+
def _parse_slice(self, key: slice) -> Tuple[int, int, int]:
|
|
210
|
+
"""
|
|
211
|
+
Parse the slice to get the start, stop and step
|
|
212
|
+
:param key: slice. The slice to parse.
|
|
213
|
+
:return: start, stop, step
|
|
214
|
+
"""
|
|
215
|
+
_start = key.start if key.start is not None else 0
|
|
216
|
+
_stop = key.stop if key.stop is not None else self.size
|
|
217
|
+
_step = key.step if key.step is not None else 1
|
|
218
|
+
return _start, _stop, _step
|
|
219
|
+
|
|
52
220
|
def __str__(self):
|
|
53
221
|
txt = str(self._sr)
|
|
54
222
|
|
|
@@ -61,6 +229,9 @@ class _ExcelIndexsLike_1DArray:
|
|
|
61
229
|
|
|
62
230
|
return txt
|
|
63
231
|
|
|
232
|
+
def __len__(self):
|
|
233
|
+
return self.size
|
|
234
|
+
|
|
64
235
|
@property
|
|
65
236
|
def size(self):
|
|
66
237
|
return self._sr.shape[0]
|
|
@@ -80,10 +251,12 @@ class _ExcelIndexsLike_1DArray:
|
|
|
80
251
|
:return: type, real_index
|
|
81
252
|
* type:
|
|
82
253
|
' ': 简单索引类型
|
|
254
|
+
'n': 全选类型
|
|
83
255
|
"s": 文本ABC索引类型
|
|
84
256
|
"i": 文本123索引类型
|
|
85
257
|
"t??": slice类型, ? 可以是 ' nis'中的一个
|
|
86
258
|
"""
|
|
259
|
+
skey = skey.upper()
|
|
87
260
|
# NOTE: index_from_string will thorw warning if the type is not matched
|
|
88
261
|
# 先考虑是否是slice
|
|
89
262
|
if ':' in skey:
|
|
@@ -94,20 +267,22 @@ class _ExcelIndexsLike_1DArray:
|
|
|
94
267
|
_t1 = 'i' if l1.isdigit() else 's'
|
|
95
268
|
|
|
96
269
|
start, end = index_from_string(l0, should=self._CLS_DEFAULT_INDEX_TYPE, only_int=True), index_from_string(l1, should=self._CLS_DEFAULT_INDEX_TYPE, only_int=True)
|
|
97
|
-
return f"t{_t0}{_t1}", slice(start, end + 1)
|
|
270
|
+
return f"t{_t0}{_t1}", slice(start, end + 1) if start <= end else slice(end, start + 1) # NOTE: 'A:C' with 'C:A' is the same
|
|
98
271
|
|
|
99
272
|
if _PAT_COORD.match(skey):
|
|
100
273
|
raise ValueError(f"Unexpected index: {skey}.")
|
|
101
274
|
_type = 'i' if skey.isdigit() else 's'
|
|
102
275
|
return _type, index_from_string(skey, should=self._CLS_DEFAULT_INDEX_TYPE, only_int=True) - 1
|
|
103
276
|
|
|
104
|
-
def
|
|
277
|
+
def _dim1_parse_index_key(self, key, *, err_when_slice=False) -> Tuple[U[str, None], U[int, slice]]:
|
|
105
278
|
"""
|
|
106
279
|
Parse the key to get the type and index
|
|
107
280
|
:param key:
|
|
281
|
+
:param err_when_slice: bool. If True, raise error when slice is found.
|
|
108
282
|
:return: type, index
|
|
109
283
|
* type:
|
|
110
284
|
' ': 简单索引类型
|
|
285
|
+
'n': 全选类型
|
|
111
286
|
"s": 文本ABC索引类型
|
|
112
287
|
"i": 文本123索引类型
|
|
113
288
|
"t??": slice类型, ? 可以是 ' nis'中的一个
|
|
@@ -121,53 +296,47 @@ class _ExcelIndexsLike_1DArray:
|
|
|
121
296
|
# NOTE:交给专用于处理字符串索引的函数处理
|
|
122
297
|
return self.__dim1_parse_str_index(key)
|
|
123
298
|
elif isinstance(key, slice):
|
|
299
|
+
if err_when_slice:
|
|
300
|
+
raise ValueError(f"Unexpected slice index: {key}. (Because param err_when_slice=True).")
|
|
124
301
|
_a, _b, _step = key.start, key.stop, key.step
|
|
302
|
+
_atype, _aindex, _btype, _bindex = 'n', None, 'n', None
|
|
125
303
|
if _a is None and _b is None:
|
|
126
304
|
return "tnn", key
|
|
127
|
-
_atype, _aindex, _btype, _bindex, _any_flag = 'n', None, 'n', None, False
|
|
128
305
|
if _a is not None:
|
|
129
|
-
_atype, _aindex = self.
|
|
130
|
-
_any_flag = True
|
|
306
|
+
_atype, _aindex = self._dim1_parse_index_key(_a)
|
|
131
307
|
if _b is not None:
|
|
132
|
-
_btype, _bindex = self.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
308
|
+
_btype, _bindex = self._dim1_parse_index_key(_b)
|
|
309
|
+
if _atype != _btype:
|
|
310
|
+
raise ValueError(f"Unexpected index: '{key}'. Which is not in the same type.")
|
|
311
|
+
|
|
136
312
|
return f"t{_atype}{_btype}", slice(_aindex, _bindex, _step)
|
|
137
313
|
else:
|
|
138
314
|
raise ValueError(f"Unsupported type: {type(key)}")
|
|
139
315
|
|
|
140
316
|
def __getitem__(self, key):
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
:param key:
|
|
144
|
-
only unit types:
|
|
145
|
-
int -> value
|
|
146
|
-
alpha -> value
|
|
147
|
-
slice -> sub Series (new CLS)
|
|
148
|
-
:return:
|
|
149
|
-
"""
|
|
150
|
-
_stype, _index = self.__dim1_parse_index_key(key)
|
|
317
|
+
_stype, _index = self._dim1_parse_index_key(key)
|
|
151
318
|
if _stype == 'n':
|
|
152
|
-
return self
|
|
153
|
-
elif _stype[0] != 't':
|
|
319
|
+
return self.copy() # None or ... Type
|
|
320
|
+
elif _stype[0] != 't': # return single value directly
|
|
154
321
|
return self._sr.iloc[_index]
|
|
155
322
|
else:
|
|
156
|
-
return self.
|
|
323
|
+
return self._construct_new_from_index(_index) # slice Type
|
|
157
324
|
|
|
158
325
|
def __setitem__(self, key, value):
|
|
159
|
-
_stype, _index = self.
|
|
326
|
+
_stype, _index = self._dim1_parse_index_key(key)
|
|
160
327
|
if _stype == 'n':
|
|
161
|
-
_index = slice()
|
|
162
|
-
self._sr.iloc[_index] = value
|
|
328
|
+
_index = slice(None)
|
|
329
|
+
self._sr.iloc[_index] = value # NOTE: Set value won't change the shape. So no need to construct new one.
|
|
163
330
|
|
|
164
331
|
def __delitem__(self, key):
|
|
165
|
-
_stype, _index = self.
|
|
332
|
+
_stype, _index = self._dim1_parse_index_key(key)
|
|
166
333
|
if _stype == 'n':
|
|
167
334
|
self.clear()
|
|
168
335
|
else:
|
|
169
|
-
self.
|
|
170
|
-
|
|
336
|
+
self._construct_new_from_delete(_index, inplace=True)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class ExcelIndexsLike_1DArray(_ExcelIndexsLike_1DArray_Base):
|
|
171
340
|
|
|
172
341
|
def append(self, *values):
|
|
173
342
|
"""
|
|
@@ -175,33 +344,30 @@ class _ExcelIndexsLike_1DArray:
|
|
|
175
344
|
:param values: Any. The values to append.
|
|
176
345
|
:return: None
|
|
177
346
|
"""
|
|
178
|
-
self.
|
|
179
|
-
self._reindex()
|
|
347
|
+
self._construct_new_from_insert(self.size, *values, inplace=True)
|
|
180
348
|
|
|
181
|
-
def insert(self, index, *values):
|
|
349
|
+
def insert(self, index: Union[str, int], *values):
|
|
182
350
|
"""
|
|
183
351
|
Insert values before the index
|
|
184
|
-
:param index: int|str. The index to insert before. (Can be a column-letter string)
|
|
352
|
+
:param index: int|str. The index to insert before. (Can be a column-letter string) But not support slice.
|
|
185
353
|
:param values: Any. The values to insert.
|
|
186
354
|
:return: None
|
|
187
355
|
"""
|
|
188
|
-
_stype, _index = self.
|
|
356
|
+
_stype, _index = self._dim1_parse_index_key(index)
|
|
189
357
|
if _stype[0] == "t" or _stype == "n":
|
|
190
358
|
raise ValueError(f"Here only accept non-slice index In {self.__class__.__name__}. But got {index}")
|
|
191
359
|
|
|
192
|
-
|
|
193
|
-
self._sr = pd.Series(_construct_list)
|
|
194
|
-
self._reindex()
|
|
360
|
+
self._construct_new_from_insert(_index, *values, inplace=True)
|
|
195
361
|
|
|
196
|
-
def pop(self, index, n: int = 1):
|
|
362
|
+
def pop(self, index: Union[str, int], n: int = 1):
|
|
197
363
|
"""
|
|
198
364
|
Remove and return the value at the index
|
|
199
|
-
:param index: int|str. The index to remove. (Can be a column-letter string)
|
|
365
|
+
:param index: int|str. The index to remove. (Can be a column-letter string) But not support slice.
|
|
200
366
|
:param n: int. The number of values to remove.
|
|
201
367
|
- if index + n > size, remove until the end.
|
|
202
368
|
:return: Any. The removed value. (If n > 1, return a list of values)
|
|
203
369
|
"""
|
|
204
|
-
_stype, _index = self.
|
|
370
|
+
_stype, _index = self._dim1_parse_index_key(index)
|
|
205
371
|
if _stype[0] == "t" or _stype == "n":
|
|
206
372
|
raise ValueError(f"Here only accept non-slice index In {self.__class__.__name__}. But got {index}")
|
|
207
373
|
|
|
@@ -210,9 +376,7 @@ class _ExcelIndexsLike_1DArray:
|
|
|
210
376
|
end = self.size
|
|
211
377
|
|
|
212
378
|
_poped = self._sr.iloc[_index:end].to_list()
|
|
213
|
-
|
|
214
|
-
self._sr = pd.Series(_construct_list)
|
|
215
|
-
self._reindex()
|
|
379
|
+
self._construct_new_from_delete(slice(_index, end), inplace=True)
|
|
216
380
|
if n == 1:
|
|
217
381
|
return _poped[0]
|
|
218
382
|
return _poped
|
|
@@ -223,10 +387,7 @@ class _ExcelIndexsLike_1DArray:
|
|
|
223
387
|
:param other: Series/List/NumpyArray/ExcelIndexed-1D
|
|
224
388
|
:return: None
|
|
225
389
|
"""
|
|
226
|
-
|
|
227
|
-
other = self.__class__(other)
|
|
228
|
-
self._sr = self._sr._append(other._sr, ignore_index=True)
|
|
229
|
-
self._reindex()
|
|
390
|
+
self._construct_new_from_insert(self.size, *other, inplace=True)
|
|
230
391
|
|
|
231
392
|
def index(self, value):
|
|
232
393
|
"""
|
|
@@ -261,22 +422,21 @@ class _ExcelIndexsLike_1DArray:
|
|
|
261
422
|
Reverse the Series in place.
|
|
262
423
|
:return: None
|
|
263
424
|
"""
|
|
264
|
-
self.
|
|
265
|
-
self._reindex()
|
|
425
|
+
self._construct_new_from_index(slice(None, None, -1), inplace=True)
|
|
266
426
|
|
|
267
427
|
def clear(self):
|
|
268
428
|
"""
|
|
269
429
|
Clear the Series.
|
|
270
430
|
:return: None
|
|
271
431
|
"""
|
|
272
|
-
self.
|
|
432
|
+
self._construct_new_from_delete(slice(None), inplace=True)
|
|
273
433
|
|
|
274
434
|
def copy(self):
|
|
275
435
|
"""
|
|
276
436
|
Return a shallow copy of the Series.
|
|
277
437
|
:return: Series. A shallow copy of the Series.
|
|
278
438
|
"""
|
|
279
|
-
return self.__class__(self._sr.copy(), copy=False)
|
|
439
|
+
return self.__class__(self._sr.copy(), copy=False) # NOTE: copy won't change the shape. So no need to construct new one.
|
|
280
440
|
|
|
281
441
|
def sort(self, key=None, reverse=False):
|
|
282
442
|
"""
|
|
@@ -287,8 +447,8 @@ class _ExcelIndexsLike_1DArray:
|
|
|
287
447
|
"""
|
|
288
448
|
_construct_list = self._sr.to_list()
|
|
289
449
|
_construct_list.sort(key=key, reverse=reverse)
|
|
290
|
-
self._sr = pd.Series(_construct_list)
|
|
291
|
-
self.
|
|
450
|
+
self._sr = pd.Series(_construct_list) # NOTE: sort won't change the shape. So no need to construct new one.
|
|
451
|
+
self._reindexself()
|
|
292
452
|
|
|
293
453
|
def __iter__(self):
|
|
294
454
|
return iter(self._sr)
|
|
@@ -297,7 +457,7 @@ class _ExcelIndexsLike_1DArray:
|
|
|
297
457
|
return value in self._sr
|
|
298
458
|
|
|
299
459
|
def __eq__(self, other):
|
|
300
|
-
if not isinstance(other,
|
|
460
|
+
if not isinstance(other, ExcelIndexsLike_1DArray):
|
|
301
461
|
other = self.__class__(other)
|
|
302
462
|
return self._sr.equals(other._sr)
|
|
303
463
|
|
|
@@ -316,14 +476,14 @@ class _ExcelIndexsLike_1DArray:
|
|
|
316
476
|
return self.__class__(_construct_list)
|
|
317
477
|
|
|
318
478
|
|
|
319
|
-
class Row(
|
|
479
|
+
class Row(ExcelIndexsLike_1DArray): # Like 1: (A B C D)
|
|
320
480
|
_CLS_DEFAULT_INDEX_TYPE = "s"
|
|
321
481
|
|
|
322
482
|
def __str__(self):
|
|
323
483
|
return "Row Series:\n" + super().__str__() + "\n"
|
|
324
484
|
|
|
325
485
|
|
|
326
|
-
class Col(
|
|
486
|
+
class Col(ExcelIndexsLike_1DArray): # Like A: (1 2 3 4)
|
|
327
487
|
_CLS_DEFAULT_INDEX_TYPE = "i"
|
|
328
488
|
|
|
329
489
|
def __str__(self):
|
|
@@ -362,7 +522,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
362
522
|
warnings.warn(
|
|
363
523
|
f"Table's input should be rows, but got a column. Regarded as a row.(at index:{i})"
|
|
364
524
|
)
|
|
365
|
-
elif isinstance(_l,
|
|
525
|
+
elif isinstance(_l, ExcelIndexsLike_1DArray):
|
|
366
526
|
_construct_list.append(_l._sr.to_list())
|
|
367
527
|
elif isinstance(_l, list):
|
|
368
528
|
_construct_list.append(_l.copy())
|
|
@@ -431,10 +591,8 @@ class _ExcelIndexsLike_2DArray:
|
|
|
431
591
|
_cnd[lt.ir, lt.ic] = ':' + _cnd[lt.ir, lt.ic][1:]
|
|
432
592
|
_unhandled_mask[lt.ir:rb.ir + 1, lt.ic:rb.ic + 1] = False
|
|
433
593
|
|
|
434
|
-
|
|
435
594
|
return str(pd.DataFrame(_cnd, index=self._df.index, columns=self._df.columns, copy=False))
|
|
436
595
|
|
|
437
|
-
|
|
438
596
|
def __repr__(self):
|
|
439
597
|
c = Coord(self._df.shape[1] - 1, self._df.shape[0] - 1)
|
|
440
598
|
return f"Table[:'{c.letter}']"
|
|
@@ -776,6 +934,12 @@ class _ExcelIndexsLike_2DArray:
|
|
|
776
934
|
sr, sc = self._df.shape
|
|
777
935
|
sr, sc = sr - 1, sc - 1
|
|
778
936
|
|
|
937
|
+
if isinstance(ir, slice):
|
|
938
|
+
ir = ir.stop if ir.stop is not None else sr
|
|
939
|
+
|
|
940
|
+
if isinstance(ic, slice):
|
|
941
|
+
ic = ic.stop if ic.stop is not None else sc
|
|
942
|
+
|
|
779
943
|
if ir is None:
|
|
780
944
|
ir = sr
|
|
781
945
|
if ic is None:
|
|
@@ -787,9 +951,9 @@ class _ExcelIndexsLike_2DArray:
|
|
|
787
951
|
|
|
788
952
|
# enlarge the table
|
|
789
953
|
_2d_list = self._df.values.tolist()
|
|
790
|
-
if ir
|
|
954
|
+
if ir > sr:
|
|
791
955
|
_2d_list += [[fill] * sc for _ in range(ir - sr)]
|
|
792
|
-
if ic
|
|
956
|
+
if ic > sc:
|
|
793
957
|
for _l in _2d_list:
|
|
794
958
|
_l.extend([fill] * (ic - sc))
|
|
795
959
|
self._reconstruct(_2d_list, *self._other_raws, copy=False)
|
|
@@ -842,7 +1006,7 @@ class _ExcelIndexsLike_2DArray:
|
|
|
842
1006
|
_i0 = slice(None)
|
|
843
1007
|
elif _is_i1_slice and _i1.start is None and _i1.stop is None:
|
|
844
1008
|
_i1 = None
|
|
845
|
-
else: #
|
|
1009
|
+
else: # 元素赋值
|
|
846
1010
|
self._enlarge(_i0, _i1, fill=np.nan)
|
|
847
1011
|
self._df.iloc[_i0, _i1] = value
|
|
848
1012
|
|
|
@@ -940,7 +1104,6 @@ class _ExcelIndexsLike_2DArray:
|
|
|
940
1104
|
else:
|
|
941
1105
|
self._df.iloc[_i0, _i1] = np.nan
|
|
942
1106
|
|
|
943
|
-
|
|
944
1107
|
def append(self, *values, axis=0):
|
|
945
1108
|
"""
|
|
946
1109
|
Append Rows to the end of the table
|
|
@@ -1002,9 +1165,9 @@ class _ExcelIndexsLike_2DArray:
|
|
|
1002
1165
|
|
|
1003
1166
|
# 先考虑扩张
|
|
1004
1167
|
if _r < _new_r:
|
|
1005
|
-
self.append([[fill.copy() if _has_fill_copy else fill] * _c for _ in range(_new_r - _r)], axis=0)
|
|
1168
|
+
self.append(*[[fill.copy() if _has_fill_copy else fill] * _c for _ in range(_new_r - _r)], axis=0)
|
|
1006
1169
|
if _c < _new_c:
|
|
1007
|
-
self.append([fill.copy() if _has_fill_copy else fill for _ in range(_new_c - _c)], axis=1)
|
|
1170
|
+
self.append(*[fill.copy() if _has_fill_copy else fill for _ in range(_new_c - _c)], axis=1)
|
|
1008
1171
|
|
|
1009
1172
|
# 再考虑缩小 NOTE: 缩小不必考虑label的问题
|
|
1010
1173
|
if _r > _new_r:
|
|
@@ -1012,7 +1175,6 @@ class _ExcelIndexsLike_2DArray:
|
|
|
1012
1175
|
if _c > _new_c:
|
|
1013
1176
|
self._reconstruct(self._df.iloc[:, :_new_c], *self._other_raws, copy=False)
|
|
1014
1177
|
|
|
1015
|
-
|
|
1016
1178
|
def __iter__(self):
|
|
1017
1179
|
for _r in self._df.values.tolist():
|
|
1018
1180
|
yield _r
|
|
@@ -1033,6 +1195,8 @@ class _ExcelIndexsLike_2DArray:
|
|
|
1033
1195
|
return not self._df.equals(other._df)
|
|
1034
1196
|
return np.array(self._df) != other
|
|
1035
1197
|
|
|
1198
|
+
def __len__(self):
|
|
1199
|
+
return self.shape[0]
|
|
1036
1200
|
|
|
1037
1201
|
|
|
1038
1202
|
class StyleRow(Row):
|
|
@@ -1046,6 +1210,7 @@ class StyleRow(Row):
|
|
|
1046
1210
|
for ic in range(self.shape[0]):
|
|
1047
1211
|
self._sr.iloc[ic][key] = value
|
|
1048
1212
|
|
|
1213
|
+
|
|
1049
1214
|
class StyleCol(Col):
|
|
1050
1215
|
def set(self, key, value):
|
|
1051
1216
|
"""
|
|
@@ -1057,9 +1222,11 @@ class StyleCol(Col):
|
|
|
1057
1222
|
for ir in range(self.shape[0]):
|
|
1058
1223
|
self._sr.iloc[ir][key] = value
|
|
1059
1224
|
|
|
1225
|
+
|
|
1060
1226
|
class StyleTable(_ExcelIndexsLike_2DArray):
|
|
1061
1227
|
_ROW_CLS = StyleRow
|
|
1062
1228
|
_COL_CLS = StyleCol
|
|
1229
|
+
|
|
1063
1230
|
def set(self, key, value):
|
|
1064
1231
|
"""
|
|
1065
1232
|
Set the style of each cell
|
|
@@ -1071,11 +1238,12 @@ class StyleTable(_ExcelIndexsLike_2DArray):
|
|
|
1071
1238
|
for ic in range(self.shape[1]):
|
|
1072
1239
|
self._df.iloc[ir, ic][key] = value
|
|
1073
1240
|
|
|
1241
|
+
|
|
1074
1242
|
class Table(_ExcelIndexsLike_2DArray):
|
|
1075
1243
|
# def __init__(self, _from=None, *, copy: bool = True):
|
|
1076
|
-
def __init__(self, data=None, styles=None, merges:list=None, *, copy: bool = True):
|
|
1244
|
+
def __init__(self, data=None, styles=None, merges: list = None, *, copy: bool = True):
|
|
1077
1245
|
super().__init__(data, copy=copy)
|
|
1078
|
-
self._styles = StyleTable() if styles is None else StyleTable(styles)
|
|
1246
|
+
self._styles = StyleTable() if styles is None else (styles.copy() if isinstance(styles, StyleTable) else StyleTable(styles))
|
|
1079
1247
|
self._merges = []
|
|
1080
1248
|
|
|
1081
1249
|
self.__raw_merges = merges
|
|
@@ -1101,7 +1269,7 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1101
1269
|
def merges(self):
|
|
1102
1270
|
return self._merges
|
|
1103
1271
|
|
|
1104
|
-
def merge(self, key:U[int, str, slice, None], key1:U[int, str, slice, None]=EMPTY_INDEX):
|
|
1272
|
+
def merge(self, key: U[int, str, slice, None], key1: U[int, str, slice, None] = EMPTY_INDEX, *, border: Border = None):
|
|
1105
1273
|
if key1 is not EMPTY_INDEX:
|
|
1106
1274
|
key = key, key1
|
|
1107
1275
|
|
|
@@ -1139,6 +1307,9 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1139
1307
|
|
|
1140
1308
|
self._merges.append((lt, rb))
|
|
1141
1309
|
|
|
1310
|
+
if border is not None:
|
|
1311
|
+
self._styles[key].set(TYPE_BORDER, border)
|
|
1312
|
+
|
|
1142
1313
|
def _reconstruct(self, *args, **kwargs):
|
|
1143
1314
|
"""
|
|
1144
1315
|
用于缩小或扩大表格时自动同步调整styles
|
|
@@ -1251,6 +1422,7 @@ class Table(_ExcelIndexsLike_2DArray):
|
|
|
1251
1422
|
|
|
1252
1423
|
|
|
1253
1424
|
def __1d_test(_print=True):
|
|
1425
|
+
|
|
1254
1426
|
d = [1, 2, 3, 4, 5]
|
|
1255
1427
|
|
|
1256
1428
|
r = Row(d)
|
|
@@ -1281,6 +1453,16 @@ def __1d_test(_print=True):
|
|
|
1281
1453
|
if _print: print(f"删除测试:\n{r}")
|
|
1282
1454
|
|
|
1283
1455
|
|
|
1456
|
+
def _1d_unit_test(_print=True):
|
|
1457
|
+
tc = TimeRecorder()
|
|
1458
|
+
__1d_test(_print)
|
|
1459
|
+
warnings.filterwarnings('ignore')
|
|
1460
|
+
for i in range(999):
|
|
1461
|
+
__1d_test(_print)
|
|
1462
|
+
warnings.filterwarnings('default')
|
|
1463
|
+
print(f"测试结束,耗时: {tc.dms(1000)} ms / 测试")
|
|
1464
|
+
|
|
1465
|
+
|
|
1284
1466
|
def __2d_test(_print=True):
|
|
1285
1467
|
import colorama
|
|
1286
1468
|
r0 = Col([1, 2, 3, 4, 5])
|
|
@@ -1352,4 +1534,4 @@ if __name__ == '__main__':
|
|
|
1352
1534
|
# print(f"Finish Test. Cost:{round((time.time() - _a) * 1000, 2)} ms")
|
|
1353
1535
|
# exit(0)
|
|
1354
1536
|
|
|
1355
|
-
|
|
1537
|
+
_1d_unit_test(False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: tpltable
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: define "Excel" "Table|StyleTable" "Row|Col|StyleRow|StyleCol". Can dynamic modify excel data & styles. Support multiple kinds of indexes. (70%)
|
|
5
5
|
Author-email: 2229066748@qq.com
|
|
6
6
|
Maintainer: Eagle'sBaby
|
tpltable-0.3.2/tpltable/core.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from tpltable.basic import *
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|