table-stream 0.1.4__tar.gz → 0.1.6__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.
- {table_stream-0.1.4 → table_stream-0.1.6}/PKG-INFO +2 -4
- table_stream-0.1.6/pyproject.toml +22 -0
- table_stream-0.1.6/table_stream/__init__.py +12 -0
- table_stream-0.1.4/table_stream/erros/__init__.py → table_stream-0.1.6/table_stream/erros.py +12 -0
- table_stream-0.1.6/table_stream/hash_map.py +307 -0
- table_stream-0.1.6/table_stream/io.py +228 -0
- table_stream-0.1.6/table_stream/notifier.py +170 -0
- table_stream-0.1.6/table_stream/pattner/__init__.py +7 -0
- table_stream-0.1.6/table_stream/pattner/adapter.py +40 -0
- table_stream-0.1.4/table_stream/types/pattner/observer/__init__.py → table_stream-0.1.6/table_stream/pattner/observer.py +62 -46
- table_stream-0.1.6/table_stream/sheet/__init__.py +10 -0
- table_stream-0.1.6/table_stream/sheet/check_mod.py +9 -0
- table_stream-0.1.6/table_stream/sheet/load.py +340 -0
- table_stream-0.1.6/table_stream/sheet/options.py +73 -0
- table_stream-0.1.4/table_stream/sheet/parse/__init__.py → table_stream-0.1.6/table_stream/sheet/parse.py +94 -70
- table_stream-0.1.6/table_stream/sheet/save.py +168 -0
- table_stream-0.1.6/table_stream/state.py +284 -0
- table_stream-0.1.6/table_stream/sysfiles.py +529 -0
- table_stream-0.1.6/table_stream/thread.py +138 -0
- table_stream-0.1.4/table_stream/types/workbook/__init__.py → table_stream-0.1.6/table_stream/workbook.py +1 -2
- {table_stream-0.1.4 → table_stream-0.1.6}/table_stream.egg-info/PKG-INFO +2 -4
- table_stream-0.1.6/table_stream.egg-info/SOURCES.txt +25 -0
- table_stream-0.1.6/table_stream.egg-info/requires.txt +2 -0
- table_stream-0.1.4/pyproject.toml +0 -41
- table_stream-0.1.4/table_stream/__init__.py +0 -11
- table_stream-0.1.4/table_stream/base/__init__.py +0 -2
- table_stream-0.1.4/table_stream/base/hash_map.py +0 -160
- table_stream-0.1.4/table_stream/base/mapping.py +0 -153
- table_stream-0.1.4/table_stream/sheet/__init__.py +0 -10
- table_stream-0.1.4/table_stream/sheet/csv/__init__.py +0 -97
- table_stream-0.1.4/table_stream/sheet/excel/__init__.py +0 -45
- table_stream-0.1.4/table_stream/sheet/excel/_col_index.py +0 -25
- table_stream-0.1.4/table_stream/sheet/interface/__init__.py +0 -63
- table_stream-0.1.4/table_stream/sheet/load_adapter/__init__.py +0 -64
- table_stream-0.1.4/table_stream/sheet/ods/__init__.py +0 -48
- table_stream-0.1.4/table_stream/thread/__init__.py +0 -369
- table_stream-0.1.4/table_stream/types/__init__.py +0 -6
- table_stream-0.1.4/table_stream/types/pattner/__init__.py +0 -9
- table_stream-0.1.4/table_stream/types/pattner/adapter/__init__.py +0 -82
- table_stream-0.1.4/table_stream/types/pattner/mediator/__init__.py +0 -74
- table_stream-0.1.4/table_stream.egg-info/SOURCES.txt +0 -27
- table_stream-0.1.4/table_stream.egg-info/requires.txt +0 -4
- {table_stream-0.1.4 → table_stream-0.1.6}/README.md +0 -0
- {table_stream-0.1.4 → table_stream-0.1.6}/setup.cfg +0 -0
- {table_stream-0.1.4 → table_stream-0.1.6}/table_stream.egg-info/dependency_links.txt +0 -0
- {table_stream-0.1.4 → table_stream-0.1.6}/table_stream.egg-info/top_level.txt +0 -0
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: table_stream
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.6
|
|
4
4
|
Summary: Tipos de dados e implementações para o uso em tabelas.
|
|
5
|
-
Requires-Python: >=3.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
|
-
Requires-Dist: odfpy>=1.4.1
|
|
8
7
|
Requires-Dist: openpyxl>=3.1.5
|
|
9
8
|
Requires-Dist: pandas>=3.0.0
|
|
10
|
-
Requires-Dist: soup-files>=1.4.2
|
|
11
9
|
|
|
12
10
|
# table
|
|
13
11
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "table_stream"
|
|
3
|
+
version = "0.1.6"
|
|
4
|
+
description = "Tipos de dados e implementações para o uso em tabelas."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"openpyxl>=3.1.5",
|
|
9
|
+
"pandas>=3.0.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[dependency-groups]
|
|
13
|
+
dev = [
|
|
14
|
+
"build>=1.4.0",
|
|
15
|
+
"twine>=6.2.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[tool.setuptools.packages.find]
|
|
19
|
+
where = ["."]
|
|
20
|
+
include = ["table_stream*"]
|
|
21
|
+
|
|
22
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .hash_map import HashMap, hashMapName, HashMapDataFrame, HashMapDict, ArrayList, K, T
|
|
2
|
+
from .notifier import ValueNotifier, ChangeNotifier, Provider, Consumer
|
|
3
|
+
from .thread import (
|
|
4
|
+
ControllerThread, ThreadStateContext, ConsumerThread, exceptionValue, stateName,
|
|
5
|
+
EventState, InvalidStateTransition,
|
|
6
|
+
)
|
|
7
|
+
from .sheet import (
|
|
8
|
+
SheetLoader, InterfaceSheetLoad, ParserData, CsvOptions, SplitData,
|
|
9
|
+
csvEncoding, csvSeparator, OptionsIOSheet, SaveData,
|
|
10
|
+
FactorySaveData, FactorySheetLoader, SheetType,
|
|
11
|
+
)
|
|
12
|
+
from .state import ContextState, EventState
|
table_stream-0.1.4/table_stream/erros/__init__.py → table_stream-0.1.6/table_stream/erros.py
RENAMED
|
@@ -15,6 +15,18 @@ class CoreException(Exception):
|
|
|
15
15
|
raise self
|
|
16
16
|
|
|
17
17
|
|
|
18
|
+
class ValueNotifyNullError(CoreException):
|
|
19
|
+
|
|
20
|
+
def __init__(self, message: str = 'Notificação nula...') -> None:
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class UnsuportedSheetError(CoreException):
|
|
25
|
+
|
|
26
|
+
def __init__(self, message: str = 'Planilha inválida...') -> None:
|
|
27
|
+
super().__init__(message)
|
|
28
|
+
|
|
29
|
+
|
|
18
30
|
#============================================================#
|
|
19
31
|
# Erros relacionados a HashMap
|
|
20
32
|
#============================================================#
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import abstractmethod, ABCMeta
|
|
3
|
+
from typing import Any, Callable, TypeVar, Union, Literal
|
|
4
|
+
import json
|
|
5
|
+
from pandas import DataFrame
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
T = TypeVar('T')
|
|
9
|
+
K = TypeVar('K')
|
|
10
|
+
hashMapName = Literal['DATAFRAME', 'DICT']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ArrayList[T](list):
|
|
14
|
+
|
|
15
|
+
def __init__(self, items: list[T] | None = None):
|
|
16
|
+
if items is None:
|
|
17
|
+
items = list()
|
|
18
|
+
super().__init__(items)
|
|
19
|
+
|
|
20
|
+
def set_items(self, items: list[T]):
|
|
21
|
+
self.clear()
|
|
22
|
+
super().__init__(items)
|
|
23
|
+
|
|
24
|
+
def for_each(self, func: Callable[[T], Any | None]) -> None:
|
|
25
|
+
for x in self: func(x)
|
|
26
|
+
|
|
27
|
+
def apply_command(self, func: Callable[[T], Any]) -> ArrayList[Any]:
|
|
28
|
+
return ArrayList([func(x) for x in self])
|
|
29
|
+
|
|
30
|
+
def size(self) -> int:
|
|
31
|
+
return len(self)
|
|
32
|
+
|
|
33
|
+
def empty(self) -> bool:
|
|
34
|
+
return self.size() == 0
|
|
35
|
+
|
|
36
|
+
def get_first(self) -> T:
|
|
37
|
+
return self[0]
|
|
38
|
+
|
|
39
|
+
def get_last(self) -> T:
|
|
40
|
+
return self[-1]
|
|
41
|
+
|
|
42
|
+
def hash(self) -> int:
|
|
43
|
+
return hash(tuple(self))
|
|
44
|
+
|
|
45
|
+
def contains(self, _o: T) -> bool:
|
|
46
|
+
for i in self:
|
|
47
|
+
if i == _o:
|
|
48
|
+
return True
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
#========================================================#
|
|
53
|
+
# Base para HashMap().
|
|
54
|
+
#========================================================#
|
|
55
|
+
class HashMap[K, T](metaclass=ABCMeta):
|
|
56
|
+
|
|
57
|
+
def __repr__(self) -> str:
|
|
58
|
+
return f"<HashMap()> {self.header()}"
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
def size_values(self) -> int:
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def size_header(self) -> int:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
@abstractmethod
|
|
69
|
+
def __getitem__(self, key: K) -> T:
|
|
70
|
+
pass
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def __setitem__(self, key: K, value: T) -> None:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@abstractmethod
|
|
77
|
+
def get_hash_map_name(self) -> hashMapName:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def get_real_hash_map(self) -> Union[dict[K, T], DataFrame]:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def set_real_hash_map(self, hash_map: dict[K, T] | DataFrame) -> None:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def clear(self) -> None:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def get_first(self) -> T:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def set_first(self, value: T) -> None:
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
@abstractmethod
|
|
101
|
+
def get_last(self) -> T:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
@abstractmethod
|
|
105
|
+
def set_last(self, value: T) -> None:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
@abstractmethod
|
|
109
|
+
def set_value(self, key: K, value: T) -> None:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
@abstractmethod
|
|
113
|
+
def get_value(self, key: K) -> T:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
@abstractmethod
|
|
117
|
+
def header(self) -> ArrayList[K]:
|
|
118
|
+
"""
|
|
119
|
+
Retornar as chaves de um dicionário ou columns de um DataFrame()
|
|
120
|
+
"""
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def values(self) -> ArrayList[T]:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def delete_items(self, keys: list[K]) -> None:
|
|
129
|
+
"""
|
|
130
|
+
Apaga chaves e valores, semelhante ao método pop().
|
|
131
|
+
"""
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
@abstractmethod
|
|
135
|
+
def to_json(self) -> str:
|
|
136
|
+
"""
|
|
137
|
+
Converte o HashMap() para uma representação no formato json.
|
|
138
|
+
"""
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
@abstractmethod
|
|
142
|
+
def to_map_str(self) -> dict[str, Any]:
|
|
143
|
+
"""
|
|
144
|
+
Converte o HashMap() para uma representação no formato dict() python.
|
|
145
|
+
"""
|
|
146
|
+
pass
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def from_json(cls, data: str) -> HashMap:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
@classmethod
|
|
153
|
+
def from_map(cls, data: Any) -> HashMap:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class HashMapDict[K, T](HashMap):
|
|
158
|
+
|
|
159
|
+
def __init__(self, _mapping: dict[K, T] | None = None) -> None:
|
|
160
|
+
super().__init__()
|
|
161
|
+
if _mapping is None:
|
|
162
|
+
_mapping = dict()
|
|
163
|
+
self._mapping: dict[K, T] = _mapping
|
|
164
|
+
|
|
165
|
+
def __repr__(self) -> str:
|
|
166
|
+
return f"<{__class__.__name__}()>\n{self.get_real_hash_map()}"
|
|
167
|
+
|
|
168
|
+
def size_values(self) -> int:
|
|
169
|
+
return len(self._mapping.values())
|
|
170
|
+
|
|
171
|
+
def size_header(self) -> int:
|
|
172
|
+
return self.header().size()
|
|
173
|
+
|
|
174
|
+
def __getitem__(self, key: K) -> T:
|
|
175
|
+
return self._mapping[key]
|
|
176
|
+
|
|
177
|
+
def __setitem__(self, key: K, value: T) -> None:
|
|
178
|
+
self._mapping[key] = value
|
|
179
|
+
|
|
180
|
+
def get_hash_map_name(self) -> Literal['DICT']:
|
|
181
|
+
return "DICT"
|
|
182
|
+
|
|
183
|
+
def get_real_hash_map(self) -> dict[K, T]:
|
|
184
|
+
return self._mapping
|
|
185
|
+
|
|
186
|
+
def set_real_hash_map(self, hash_map: dict[K, T]) -> None:
|
|
187
|
+
self._mapping = hash_map
|
|
188
|
+
|
|
189
|
+
def clear(self) -> None:
|
|
190
|
+
self._mapping.clear()
|
|
191
|
+
|
|
192
|
+
def get_first(self) -> T:
|
|
193
|
+
return self._mapping[self.header().get_first()]
|
|
194
|
+
|
|
195
|
+
def set_first(self, value: T) -> None:
|
|
196
|
+
self._mapping[self.header().get_first()] = value
|
|
197
|
+
|
|
198
|
+
def get_last(self) -> T:
|
|
199
|
+
return self._mapping[self.header().get_last()]
|
|
200
|
+
|
|
201
|
+
def set_last(self, value: T) -> None:
|
|
202
|
+
self._mapping[self.header().get_last()] = value
|
|
203
|
+
|
|
204
|
+
def set_value(self, key: K, value: T) -> None:
|
|
205
|
+
self._mapping[key] = value
|
|
206
|
+
|
|
207
|
+
def get_value(self, key: K) -> T:
|
|
208
|
+
return self._mapping[key]
|
|
209
|
+
|
|
210
|
+
def header(self) -> ArrayList[K]:
|
|
211
|
+
return ArrayList(list(self._mapping.keys()))
|
|
212
|
+
|
|
213
|
+
def values(self) -> ArrayList[T]:
|
|
214
|
+
return ArrayList(list(self._mapping.values()))
|
|
215
|
+
|
|
216
|
+
def delete_items(self, keys: list[K]) -> None:
|
|
217
|
+
for k in keys:
|
|
218
|
+
self._mapping.pop(k)
|
|
219
|
+
|
|
220
|
+
def to_json(self) -> str:
|
|
221
|
+
"""
|
|
222
|
+
Converte o mapa real para uma string JSON.
|
|
223
|
+
Nota: As chaves e valores devem ser serializáveis pelo módulo json.
|
|
224
|
+
"""
|
|
225
|
+
return json.dumps(self.to_map_str(), ensure_ascii=False, indent=4)
|
|
226
|
+
|
|
227
|
+
def to_map_str(self) -> dict[str, Any]:
|
|
228
|
+
final = dict()
|
|
229
|
+
for i in self.header():
|
|
230
|
+
final[str(i)] = str(self.get_value(i))
|
|
231
|
+
return final
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class HashMapDataFrame[K, Series](HashMap):
|
|
235
|
+
|
|
236
|
+
def __init__(self, df: DataFrame=None) -> None:
|
|
237
|
+
super().__init__()
|
|
238
|
+
if df is None:
|
|
239
|
+
df = DataFrame()
|
|
240
|
+
self._df: DataFrame = df
|
|
241
|
+
|
|
242
|
+
def size_values(self) -> int:
|
|
243
|
+
return len(self.values())
|
|
244
|
+
|
|
245
|
+
def size_header(self) -> int:
|
|
246
|
+
return len(self.header())
|
|
247
|
+
|
|
248
|
+
def __getitem__(self, key: K) -> Series:
|
|
249
|
+
return self._df[key]
|
|
250
|
+
|
|
251
|
+
def __setitem__(self, key: K, value: Series) -> None:
|
|
252
|
+
self._df[key] = value
|
|
253
|
+
|
|
254
|
+
def get_hash_map_name(self) -> Literal['DATAFRAME']:
|
|
255
|
+
return "DATAFRAME"
|
|
256
|
+
|
|
257
|
+
def get_real_hash_map(self) -> DataFrame:
|
|
258
|
+
return self._df
|
|
259
|
+
|
|
260
|
+
def set_real_hash_map(self, hash_map: DataFrame) -> None:
|
|
261
|
+
self._df = hash_map
|
|
262
|
+
|
|
263
|
+
def clear(self) -> None:
|
|
264
|
+
self._df = DataFrame()
|
|
265
|
+
|
|
266
|
+
def get_first(self) -> Series:
|
|
267
|
+
return self._df[self.header().get_first()]
|
|
268
|
+
|
|
269
|
+
def set_first(self, value: Series) -> None:
|
|
270
|
+
self._df[self.header().get_first()] = value
|
|
271
|
+
|
|
272
|
+
def get_last(self) -> Series:
|
|
273
|
+
return self._df[self.header().get_last()]
|
|
274
|
+
|
|
275
|
+
def set_last(self, value: T) -> None:
|
|
276
|
+
raise NotImplementedError
|
|
277
|
+
|
|
278
|
+
def set_value(self, key: K, value: Series) -> None:
|
|
279
|
+
self._df[key] = value
|
|
280
|
+
|
|
281
|
+
def get_value(self, key: K) -> Series:
|
|
282
|
+
return self._df[key]
|
|
283
|
+
|
|
284
|
+
def header(self) -> ArrayList[K]:
|
|
285
|
+
return ArrayList(self._df.columns.tolist())
|
|
286
|
+
|
|
287
|
+
def values(self) -> ArrayList[Any]:
|
|
288
|
+
return ArrayList(self._df.values.tolist())
|
|
289
|
+
|
|
290
|
+
def delete_items(self, keys: list[K]) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Remove as colunas informadas.
|
|
293
|
+
"""
|
|
294
|
+
# axis=1 indica que queremos remover colunas (as chaves do seu mapa)
|
|
295
|
+
# errors='ignore' evita que o código quebre caso uma chave não exista
|
|
296
|
+
# self._data.drop(columns=keys, axis=1, inplace=True, errors='ignore'
|
|
297
|
+
self._df.drop(columns=keys, inplace=True, errors="ignore", )
|
|
298
|
+
|
|
299
|
+
def to_json(self) -> str:
|
|
300
|
+
"""
|
|
301
|
+
Serializa o DataFrame para JSON.
|
|
302
|
+
Mantém estrutura por colunas.
|
|
303
|
+
"""
|
|
304
|
+
return self._df.astype(str).to_json(orient="columns", force_ascii=False, indent=4)
|
|
305
|
+
|
|
306
|
+
def to_map_str(self) -> dict[str, Any]:
|
|
307
|
+
return self._df.astype('str').to_dict()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from io import BufferedWriter, BufferedReader, BytesIO
|
|
4
|
+
from table_stream.sysfiles import File
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#----------------------------------------------------#
|
|
8
|
+
# Output Stream
|
|
9
|
+
#----------------------------------------------------#
|
|
10
|
+
class OutputStream(ABC):
|
|
11
|
+
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def get_buffer(self) -> BufferedWriter:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def write(self, b: bytes):
|
|
18
|
+
"""Escreve bytes no destino."""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def flush(self):
|
|
23
|
+
"""Força a gravação dos dados pendentes."""
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def close(self):
|
|
28
|
+
"""Fecha o recurso."""
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# Facilitador para o 'with' do Python
|
|
32
|
+
def __enter__(self):
|
|
33
|
+
return self
|
|
34
|
+
|
|
35
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
36
|
+
self.close()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FileOutputStream(OutputStream):
|
|
40
|
+
|
|
41
|
+
def __init__(self, file_path: File, append: bool = False):
|
|
42
|
+
mode = 'ab' if append else 'wb' # 'b' indica modo binário (bytes)
|
|
43
|
+
#self.file_path: File = file_path
|
|
44
|
+
self._file_buff = open(file_path.absolute(), mode)
|
|
45
|
+
|
|
46
|
+
def get_buffer(self) -> BufferedWriter:
|
|
47
|
+
return self._file_buff
|
|
48
|
+
|
|
49
|
+
def write(self, b: bytes):
|
|
50
|
+
if not isinstance(b, bytes):
|
|
51
|
+
raise TypeError("O FileOutputStream só aceita bytes!")
|
|
52
|
+
self._file_buff.write(b)
|
|
53
|
+
|
|
54
|
+
def flush(self):
|
|
55
|
+
self._file_buff.flush()
|
|
56
|
+
|
|
57
|
+
def close(self):
|
|
58
|
+
self._file_buff.close()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class BytesOutputStream(OutputStream):
|
|
62
|
+
|
|
63
|
+
def __init__(self):
|
|
64
|
+
# Inicializa um buffer vazio na memória (lista de bytes)
|
|
65
|
+
self._buffer = bytearray()
|
|
66
|
+
self._closed = False
|
|
67
|
+
|
|
68
|
+
def get_buffer(self) -> BufferedWriter:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def write(self, b: bytes):
|
|
72
|
+
if self._closed:
|
|
73
|
+
raise ValueError("Operação de escrita em stream fechada.")
|
|
74
|
+
if not isinstance(b, (bytes, bytearray)):
|
|
75
|
+
raise TypeError("O stream só aceita objetos do tipo bytes ou bytearray!")
|
|
76
|
+
|
|
77
|
+
# Adiciona os bytes ao buffer na memória
|
|
78
|
+
self._buffer.extend(b)
|
|
79
|
+
|
|
80
|
+
def flush(self):
|
|
81
|
+
# Em memória, o flush geralmente não faz nada,
|
|
82
|
+
# pois não há buffer de sistema operacional envolvido.
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def close(self):
|
|
86
|
+
self._closed = True
|
|
87
|
+
|
|
88
|
+
def to_bytes(self) -> bytes:
|
|
89
|
+
"""
|
|
90
|
+
Método específico desta classe para recuperar
|
|
91
|
+
tudo o que foi gravado até agora.
|
|
92
|
+
"""
|
|
93
|
+
return bytes(self._buffer)
|
|
94
|
+
|
|
95
|
+
def size(self) -> int:
|
|
96
|
+
"""Retorna a quantidade de bytes acumulados."""
|
|
97
|
+
return len(self._buffer)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
#----------------------------------------------------#
|
|
102
|
+
# Input Stream
|
|
103
|
+
#----------------------------------------------------#
|
|
104
|
+
|
|
105
|
+
class InputStream(ABC):
|
|
106
|
+
|
|
107
|
+
def __init__(self) -> None:
|
|
108
|
+
super().__init__()
|
|
109
|
+
|
|
110
|
+
@abstractmethod
|
|
111
|
+
def clear(self):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
@abstractmethod
|
|
115
|
+
def to_io(self) -> BytesIO:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def get_buffer(self) -> BufferedReader:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
@abstractmethod
|
|
123
|
+
def read(self, size: int = -1) -> bytes:
|
|
124
|
+
"""Lê bytes do stream."""
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
@abstractmethod
|
|
128
|
+
def close(self):
|
|
129
|
+
"""Fecha o recurso."""
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
def __enter__(self):
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
136
|
+
self.close()
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
#----------------------------------------------------#
|
|
140
|
+
# File Input Stream
|
|
141
|
+
#----------------------------------------------------#
|
|
142
|
+
class FileInputStream(InputStream):
|
|
143
|
+
|
|
144
|
+
def __init__(self, file_path: File):
|
|
145
|
+
super().__init__()
|
|
146
|
+
if not file_path.exists():
|
|
147
|
+
raise FileNotFoundError(f'{__class__.__name__} arquivo não encontrado')
|
|
148
|
+
|
|
149
|
+
self.file_path: File = file_path
|
|
150
|
+
self._file_buff: BufferedReader | None = None
|
|
151
|
+
|
|
152
|
+
def to_io(self) -> BytesIO:
|
|
153
|
+
buff = self.get_buffer()
|
|
154
|
+
buff.seek(0) # garante leitura desde o início
|
|
155
|
+
return BytesIO(buff.read())
|
|
156
|
+
|
|
157
|
+
def get_buffer(self) -> BufferedReader:
|
|
158
|
+
if self._file_buff is None:
|
|
159
|
+
self._file_buff = open(self.file_path.absolute(), 'rb')
|
|
160
|
+
return self._file_buff
|
|
161
|
+
|
|
162
|
+
def read(self, size: int = -1) -> bytes:
|
|
163
|
+
return self.get_buffer().read(size)
|
|
164
|
+
|
|
165
|
+
def close(self):
|
|
166
|
+
if self._file_buff is None:
|
|
167
|
+
return
|
|
168
|
+
self._file_buff.close()
|
|
169
|
+
|
|
170
|
+
def clear(self):
|
|
171
|
+
if self._file_buff is not None:
|
|
172
|
+
self.close()
|
|
173
|
+
self._file_buff = None
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
#----------------------------------------------------#
|
|
177
|
+
# Bytes Input Stream (memória)
|
|
178
|
+
#----------------------------------------------------#
|
|
179
|
+
class BytesInputStream(InputStream):
|
|
180
|
+
|
|
181
|
+
def __init__(self, data: bytes):
|
|
182
|
+
super().__init__()
|
|
183
|
+
if not isinstance(data, (bytes, bytearray)):
|
|
184
|
+
raise TypeError("BytesInputStream aceita apenas bytes ou bytearray!")
|
|
185
|
+
|
|
186
|
+
data.seek(0)
|
|
187
|
+
self._buffer = memoryview(data)
|
|
188
|
+
self._position = 0
|
|
189
|
+
self._closed = False
|
|
190
|
+
|
|
191
|
+
def to_io(self) -> BytesIO:
|
|
192
|
+
return BytesIO(self._buffer)
|
|
193
|
+
|
|
194
|
+
def set_bytes(self, data: bytes | bytearray):
|
|
195
|
+
self._buffer = memoryview(data)
|
|
196
|
+
|
|
197
|
+
def get_buffer(self) -> BufferedReader:
|
|
198
|
+
# Não há um buffer real compatível com BufferedReader
|
|
199
|
+
# Mantemos None ou poderíamos lançar erro
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def read(self, size: int = -1) -> bytes:
|
|
203
|
+
if self._closed:
|
|
204
|
+
raise ValueError("Operação de leitura em stream fechada.")
|
|
205
|
+
|
|
206
|
+
if size < 0:
|
|
207
|
+
size = len(self._buffer) - self._position
|
|
208
|
+
|
|
209
|
+
start = self._position
|
|
210
|
+
end = min(start + size, len(self._buffer))
|
|
211
|
+
|
|
212
|
+
self._position = end
|
|
213
|
+
return self._buffer[start:end].tobytes()
|
|
214
|
+
|
|
215
|
+
def close(self):
|
|
216
|
+
self._closed = True
|
|
217
|
+
|
|
218
|
+
def available(self) -> int:
|
|
219
|
+
"""Quantidade de bytes restantes para leitura."""
|
|
220
|
+
return len(self._buffer) - self._position
|
|
221
|
+
|
|
222
|
+
def clear(self):
|
|
223
|
+
if self._buffer is not None:
|
|
224
|
+
self.close()
|
|
225
|
+
self._buffer = None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|