pypomes-sob 0.0.1__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,16 @@
1
+ .cache
2
+ .env
3
+ .venv
4
+ .idea
5
+ .vscode
6
+ env
7
+ venv
8
+ temp
9
+ tmp
10
+ **/__pycache__
11
+ *.py[cod]
12
+ *.sh
13
+ *.ps1
14
+ /dist
15
+ /env
16
+ deploy.txt
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 GT Nunes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: pypomes_sob
3
+ Version: 0.0.1
4
+ Summary: A collection of Python pomes, penyeach (simple object module)
5
+ Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-SOB
6
+ Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-SOB/issues
7
+ Author-email: GT Nunes <wisecoder01@gmail.com>
8
+ License-File: LICENSE
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.12
13
+ Requires-Dist: pypomes-core>=2.5.1
14
+ Requires-Dist: pypomes-db>=2.3.5
15
+ Description-Content-Type: text/markdown
16
+
17
+ # PyPomes-SOB
18
+ Pomes penyeach (Simple Object module)
@@ -0,0 +1,2 @@
1
+ # PyPomes-SOB
2
+ Pomes penyeach (Simple Object module)
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = [
3
+ "hatchling>=1.27.0"
4
+ ]
5
+ build-backend = "hatchling.build"
6
+
7
+ [project]
8
+ name = "pypomes_sob"
9
+ version = "0.0.1"
10
+ authors = [
11
+ { name="GT Nunes", email="wisecoder01@gmail.com" }
12
+ ]
13
+ description = "A collection of Python pomes, penyeach (simple object module)"
14
+ readme = "README.md"
15
+ requires-python = ">=3.12"
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent"
20
+ ]
21
+ dependencies = [
22
+ "pypomes_core>=2.5.1",
23
+ "pypomes_db>=2.3.5"
24
+ ]
25
+
26
+ [project.urls]
27
+ "Homepage" = "https://github.com/TheWiseCoder/PyPomes-SOB"
28
+ "Bug Tracker" = "https://github.com/TheWiseCoder/PyPomes-SOB/issues"
@@ -0,0 +1,136 @@
1
+ # environment
2
+ cache-dir = "~/.cache/ruff"
3
+ force-exclude = true
4
+ required-version = ">=0.12"
5
+ target-version = "py312"
6
+ respect-gitignore = true
7
+ lint.task-tags = ["TODO", "FIXME", "HACK", "HAZARD"]
8
+ lint.select = [
9
+ # flake8 group
10
+ "A", # flake8-builtins - prevent shadowing of python builtins
11
+ "ANN", # flake8-annotations - enforce PEP 3107-style function
12
+ "ARG", # flake8-unused-arguments - check for unused function arguments
13
+ "ASYNC", # flake8-async - check for bad async / asyncio practices
14
+ "B", # flake8-bugbear - warnings for likely bugs / antipatterns
15
+ "C4", # flake8-comprehensions - check for bad use of list/set/dict comprehensions
16
+ "COM", # flake8-commas - rules for trailing commas
17
+ "DTZ", # flake8-datetimez - check for usage of unsafe naive datetime class
18
+ "EM", # flake8-errmsg - enforce good formatting of error messages
19
+ "FA", # flake8-future-annotations - verify use of 'from __future__ import annotations'
20
+ "FIX", # flake8-fix_me - check for FIXMEs, TODOs and other temporary developer notes
21
+ "G", # flake8-logging-format - validate logging format strings
22
+ "ICN", # flake8-import-conventions - check for proper importing and aliasing of packages
23
+ "INP", # flake8-no-pep420 - prevent PEP-420 implicit namespace packages
24
+ "INT", # flake8-gettext - check formats resolved before function call
25
+ "ISC", # flake8-implicit-str-concat - enforce correct string literal concatenation
26
+ "PIE", # flake8-pie - miscellaneous lints
27
+ "PT", # flake8-pytest-style - check common style issues or inconsistencies with pytest-based tests
28
+ "PTH", # flake8-use-pathlib - check use of functions that can be replaced by pathlib module
29
+ "PYI", # flake8-pyi - provide specializations for type hinting stub files
30
+ "Q", # flake8-quotes - linter for quotes
31
+ "RET", # flake8-return - check return values
32
+ "RSE", # flake8-raise - enforce proper coding of 'raise' statements
33
+ "S", # flake8-bandit - security testing
34
+ "SIM", # flake8-simplify - help simplify Python code
35
+ "SLF", # flake8-self - private member access linting
36
+ "SLOT", # flake8-slots - require '__slots__' to be defined for subclasses of immutable types
37
+ "TD", # flake8-todos - check TODOs in the project
38
+ "TCH", # flake8-type-checking - move imports in or out of type-checking blocks
39
+ "TID", # flake8-tidy-imports - help writing tidier imports
40
+ "YTT", # flake8-2020 - check for misuse of sys.version / sys.version_info
41
+ # pycodestyle group
42
+ "E", # pycodestyle (Errors) - enforce style conventions in PEP 8
43
+ "W", # pycodestyle (Warnings) - enforce style conventions in PEP 8
44
+ # Ruff-specific group
45
+ "RUF", # the Ruff developer's own rules
46
+ # others
47
+ "C90", # mccabe - check for excessive McCabe complexity
48
+ "D", # pydocstyle - enforce docstring conventions
49
+ "F", # Pyflakes - Python classic checker
50
+ "FLY", # flynt - enforce use of 'f-strings' for formatting
51
+ "N", # pep8-naming - enforce naming conventions in PEP 8
52
+ "NPY", # NumPy-specific rules
53
+ "PERF", # Perflint - linter for performance anti-patterns
54
+ "PGH", # pygrep-hooks - check pre-commit hooks
55
+ "PD", # pandas-vet - provide opinionated linting for pandas code
56
+ "PL", # Pylint - Python classic static code analyser
57
+ "TRY", # tryceratops - prevent exception handling antipatterns
58
+ "UP", # pyupgrade - upgrade syntax for newer versions of the language
59
+ # unused
60
+ # "AIR", # Airflow - check workflows
61
+ # "BLE", # flake8-blind-except - check for except clauses that catch all exceptions
62
+ # "CPY", # flake8-copyright - check for copyright notices in all python files
63
+ # "DJ", # flake8-django - detect bad practices on Django projects
64
+ # "ERA", # eradicate - check for commented-out code
65
+ # "EXE", # flake8-executable - ensure the executable permissions and shebangs of Python files are correctly set
66
+ # "FBT", # flake8-boolean-trap - check for boolean traps
67
+ # "I", # isort - organize imports
68
+ # "T10", # flake8-debugger - check for 'pdb;idbp' imports and set traces
69
+ # "T20", # flake8-print - check for 'print' and 'pprint' statements in python files
70
+ ]
71
+ lint.ignore = [
72
+ "ANN002", # missing type annotation for *kargs
73
+ "ANN101", # missing type annotation for 'self' in method
74
+ "ANN401", # dynamically typed expressions (typing.Any) are disallowed
75
+ "COM812", # checks for the absence of trailing commas
76
+ "COM819", # prohibited trailing commas
77
+ "D100", # missing docstring in public module
78
+ "D104", # missing docstring in public package
79
+ "D200", # one-line docstring should fit on one line
80
+ "D203", # 1 blank line required before class docstring
81
+ "D204", # 1 blank line required after class docstring
82
+ "D210", # whitespaces surrounding docstring text
83
+ "D212", # multi-line docstring summary to start at the second line
84
+ "DTZ005", # the use of datetime.datetime.now() without tz argument is not allowed
85
+ "DTZ006", # usage of 'datetime.datetime.fromtimestamp()' that do not specify a timezone
86
+ "FA102", # use PEP 585- and PEP 604-style type annotations (not applicable for Python versions >= 3.10)
87
+ "G004", # logging statement uses 'f-string'
88
+ "N815", # variable in class scope should not be mixedCase
89
+ "PERF203", # try-except within a loop incurs performance overhead (not applicable for Python >= 3.11)
90
+ "PGH004", # use specific rule codes when using 'noqa'
91
+ "PLC0415", # 'import' should be at the top level of a file
92
+ "PLR2004", # magic value used in comparison, consider replacing it with a constant variable
93
+ "PLW0603", # using the global statement to update a variable is discouraged
94
+ "PTH118", # os.{module}.join() should be replaced by Path with / operator
95
+ "RUF013", # implicit Optional in type annotations when the default parameter value is None
96
+ "RUF022", # __all__ is not sorted
97
+ "S104", # possible binding to all interfaces
98
+ "S105", # hardcoded-password-string
99
+ "S108", # probable insecure usage of temporary file or directory
100
+ "S113", # probable use of requests call without timeout
101
+ "S301", # 'pickle' and modules that wrap it can be unsafe when used to deserialize untrusted data
102
+ "S608", # possible SQL injection vector through string-based query construction
103
+ "SIM108", # use ternary operator instead of if-else-block
104
+ "TC001", # move application import into a type-checking block
105
+ "TC002", # move third-party import into a type-checking block
106
+ "TC003", # move standard library import into a type-checking block
107
+ "TRY003", # avoid specifying long messages outside the exception class
108
+ "TRY400", # use 'logging.exception' instead of 'logging.error'
109
+ ]
110
+
111
+ # rules
112
+ line-length = 120
113
+ lint.dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
114
+
115
+ [lint.flake8-bandit]
116
+ hardcoded-tmp-directory = ["/tmp"]
117
+
118
+ [lint.flake8-errmsg]
119
+ max-string-length = 60 # maximum string length for string literals in exception messages
120
+
121
+ [lint.flake8-quotes]
122
+ docstring-quotes = "double"
123
+ inline-quotes = "double"
124
+ multiline-quotes = "double"
125
+
126
+ [lint.mccabe]
127
+ max-complexity = 50 # upper limit for McCabe code complexity
128
+
129
+ [lint.pycodestyle]
130
+ max-doc-length = 120
131
+
132
+ [lint.pylint]
133
+ max-args = 30
134
+ max-branches = 40
135
+ max-returns = 1
136
+ max-statements = 150
@@ -0,0 +1,9 @@
1
+ from .sob_pomes import DB_ID, DB_TABLE, PySob
2
+
3
+ __all__ = [
4
+ "DB_ID", "DB_TABLE", "PySob"
5
+ ]
6
+
7
+ from importlib.metadata import version
8
+ __version__: str = version("pypomes_core")
9
+ __version_info__: tuple = tuple(int(i) for i in __version__.split(".") if i.isdigit())
@@ -0,0 +1,418 @@
1
+ from __future__ import annotations # allow forward references
2
+ from inspect import FrameInfo, stack
3
+ from enum import Enum, StrEnum, auto
4
+ from logging import Logger
5
+ from pypomes_core import dict_get_key, dict_stringify, validate_format_error
6
+ from pypomes_db import (
7
+ db_exists, db_count, db_select,
8
+ db_insert, db_update, db_delete
9
+ )
10
+ # 'type' is not a viable replacement for 'typing.Type', because it does not accept subclasses
11
+ from typing import Any, ClassVar, Final, Type # noqa
12
+
13
+ DB_ID: Final[str] = "id"
14
+ DB_TABLE: Final[str] = "table"
15
+
16
+
17
+ # noinspection Annotator
18
+ # ruff: noqa: UP006
19
+ class PySob:
20
+ """
21
+ Root entity.
22
+ """
23
+ class _Db(StrEnum):
24
+ TABLE = auto()
25
+ ID = auto()
26
+
27
+ db_specs: ClassVar[dict[Type[PySob], list[StrEnum]]] = {}
28
+
29
+ attrs_map: ClassVar[dict[Type[PySob], dict[StrEnum, StrEnum]]] = []
30
+
31
+ def __init__(self,
32
+ __references: list[Type[PySob]],
33
+ /,
34
+ errors: list[str] = None,
35
+ load_references: bool = False,
36
+ where_data: dict[str, Any] = None,
37
+ db_conn: Any = None,
38
+ logger: Logger = None) -> None:
39
+
40
+ self._references: list[Type[PySob]] = __references
41
+ self._logger: Logger = logger
42
+ self.id: int = 0
43
+
44
+ if where_data:
45
+ attrs: list[str] = self.get_columns()
46
+ recs: list[tuple] = db_select(errors=errors,
47
+ sel_stmt=f"SELECT {', '.join(attrs)} "
48
+ f"FROM {PySob.db_specs[self.__class__][DB_TABLE]}",
49
+ where_data=where_data,
50
+ limit_count=2,
51
+ connection=db_conn,
52
+ logger=self._logger)
53
+ msg: str | None = None
54
+ if errors:
55
+ # 201: Erro na interação com o BD em {}: {}
56
+ msg = validate_format_error(201,
57
+ f"{PySob.db_specs[self.__class__][DB_TABLE]}",
58
+ f"{'; '.join(errors)}")
59
+ elif not recs:
60
+ # 202: Nenhum registro encontrado no BD, em {} para {}
61
+ msg = validate_format_error(202,
62
+ f"{PySob.db_specs[self.__class__][DB_TABLE]}",
63
+ f"{dict_stringify(where_data)}")
64
+ elif len(recs) > 1:
65
+ # 207 Mais de um registro existente no BD, em {} para {}
66
+ msg = validate_format_error(207,
67
+ f"{PySob.db_specs[self.__class__][DB_TABLE]}",
68
+ f"{dict_stringify(where_data)}")
69
+ if msg:
70
+ errors.append(msg)
71
+ if self._logger:
72
+ self._logger.error(msg=msg)
73
+ else:
74
+ rec: tuple = recs[0]
75
+ for inx, attr in enumerate(attrs):
76
+ self.__dict__[attr] = rec[inx]
77
+ if load_references:
78
+ self.__load_references(errors=errors,
79
+ db_conn=db_conn)
80
+
81
+ def insert(self,
82
+ errors: list[str] | None,
83
+ db_conn: Any = None) -> bool:
84
+
85
+ # prepara os dados para INSERT
86
+ insert_data: dict[str, Any] = self.to_columns(omit_nulls=False)
87
+ insert_data.pop(PySob.db_specs[self.__class__][DB_ID])
88
+
89
+ # executa o INSERT
90
+ errors = errors if isinstance(errors, list) else []
91
+ rec: tuple[int] = db_insert(errors=errors,
92
+ insert_stmt=f"INSERT INTO {PySob.db_specs[self.__class__][DB_TABLE]}",
93
+ insert_data=insert_data,
94
+ return_cols={PySob.db_specs[self.__class__][DB_ID]: int},
95
+ connection=db_conn,
96
+ logger=self._logger)
97
+ if errors:
98
+ # 201: Erro na interação com o BD em {}: {}
99
+ msg = validate_format_error(201,
100
+ f"{PySob.db_specs[self.__class__][DB_TABLE]}",
101
+ f"{'; '.join(errors)}")
102
+ errors.append(msg)
103
+ if self._logger:
104
+ self._logger.error(msg=msg)
105
+ else:
106
+ self.id = rec[0]
107
+
108
+ return not errors
109
+
110
+ def update(self,
111
+ errors: list[str] | None,
112
+ load_references: bool = False,
113
+ db_conn: Any = None) -> bool | None:
114
+
115
+ # inicializa a variável de retorno
116
+ result: bool | None = None
117
+
118
+ # prepara os dados para UPDATE
119
+ update_data: dict[str, Any] = self.to_columns(omit_nulls=False)
120
+ key: int = update_data.pop(PySob.db_specs[self.__class__][DB_ID])
121
+
122
+ # execute the UPDATE operation
123
+ errors = errors if isinstance(errors, list) else []
124
+ db_update(errors=errors,
125
+ update_stmt=f"UPDATE {PySob.db_specs[self.__class__][DB_TABLE]}",
126
+ update_data=update_data,
127
+ where_data={PySob.db_specs[self.__class__][DB_ID]: key},
128
+ min_count=1,
129
+ max_count=1,
130
+ connection=db_conn,
131
+ logger=self._logger)
132
+
133
+ if not errors and load_references:
134
+ self.__load_references(errors=errors,
135
+ db_conn=db_conn)
136
+ if errors:
137
+ # 201: Erro na interação com o BD em {}: {}
138
+ msg = validate_format_error(201,
139
+ f"{PySob.db_specs[self.__class__][DB_TABLE]}",
140
+ f"{'; '.join(errors)}")
141
+ errors.append(msg)
142
+ if self._logger:
143
+ self._logger.error(msg=msg)
144
+ else:
145
+ result = True
146
+
147
+ return result
148
+
149
+ def delete(self,
150
+ errors: list[str] | None,
151
+ db_conn: Any = None) -> int | None:
152
+
153
+ where_data: dict[str, Any]
154
+ if self.id:
155
+ where_data = {PySob.db_specs[self.__class__][DB_ID]: self.id}
156
+ else:
157
+ where_data = self.to_columns(omit_nulls=True)
158
+ where_data.pop(PySob.db_specs[self.__class__][DB_ID])
159
+
160
+ # execute the DELETE operation
161
+ result: int = db_delete(errors=errors,
162
+ delete_stmt=f"DELETE FROM {PySob.db_specs[self.__class__][DB_TABLE]}",
163
+ where_data=where_data,
164
+ max_count=1,
165
+ connection=db_conn,
166
+ logger=self._logger)
167
+ if errors:
168
+ # 201: Erro na interação com o BD em {}: {}
169
+ msg = validate_format_error(201,
170
+ f"{PySob.db_specs[self.__class__][DB_TABLE]}",
171
+ f"{'; '.join(errors)}")
172
+ errors.append(msg)
173
+ if self._logger:
174
+ self._logger.error(msg=msg)
175
+ else:
176
+ self.clear()
177
+
178
+ return result
179
+
180
+ def clear(self) -> None:
181
+
182
+ for key in self.__dict__.keys():
183
+ self.__dict__[key] = None
184
+
185
+ def set(self,
186
+ data: dict[str, Any]) -> None:
187
+
188
+ for key, value in data.items():
189
+ attr: str = PySob.attrs_map[self.__class__].get(key) or key
190
+
191
+ # usa nomes de enums atribuídos como valores em 'data'
192
+ if isinstance(value, Enum) and "use_names" in value.__class__:
193
+ value = value.name
194
+
195
+ if attr in self.__dict__:
196
+ self.__dict__[attr] = value
197
+ elif self._logger:
198
+ self._logger.warning(msg=f"'{attr}' não é atributo de "
199
+ f"'{PySob.db_specs[self.__class__][DB_TABLE]}'")
200
+
201
+ def exists(self,
202
+ errors: list[str] | None,
203
+ where_data: dict[str, Any] = None,
204
+ db_conn: Any = None) -> bool | None:
205
+
206
+ if not where_data:
207
+ if self.id:
208
+ # use object's ID
209
+ where_data = {PySob.db_specs[self.__class__][DB_ID]: self.id}
210
+ else:
211
+ # use object's available data
212
+ where_data = self.to_columns(omit_nulls=True)
213
+ where_data.pop(PySob.db_specs[self.__class__][DB_ID])
214
+
215
+ return db_exists(errors=errors,
216
+ table=PySob.db_specs[self.__class__][DB_TABLE],
217
+ where_data=where_data,
218
+ connection=db_conn,
219
+ logger=self._logger)
220
+
221
+ def load(self,
222
+ errors: list[str] | None,
223
+ omit_nulls: bool,
224
+ where_data: dict[str, Any] = None,
225
+ db_conn: Any = None) -> bool:
226
+
227
+ if not where_data:
228
+ if self.id:
229
+ where_data = {PySob.db_specs[self.__class__][DB_ID]: self.id}
230
+ else:
231
+ where_data = self.to_columns(omit_nulls=omit_nulls)
232
+ where_data.pop(PySob.db_specs[self.__class__][DB_ID])
233
+
234
+ # it is acceptable that loading the object from the database might fail
235
+ attrs: list[str] = self.get_columns()
236
+ errors = errors if isinstance(errors, list) else []
237
+ recs: list[tuple] = db_select(errors=errors,
238
+ sel_stmt=f"SELECT {', '.join(attrs)} "
239
+ f"FROM {PySob.db_specs[self.__class__][DB_TABLE]}",
240
+ where_data=where_data,
241
+ limit_count=2,
242
+ connection=db_conn,
243
+ logger=self._logger)
244
+ if recs and len(recs) == 1:
245
+ rec: tuple = recs[0]
246
+ for inx, attr in enumerate(attrs):
247
+ self.__dict__[attr] = rec[inx]
248
+ elif self._logger:
249
+ self._logger.warning(msg=f"Unable to load from "
250
+ f"'{PySob.db_specs[self.__class__][DB_TABLE]}' "
251
+ f"with data {where_data}")
252
+
253
+ return recs and len(recs) == 1
254
+
255
+ def get_columns(self) -> list[str]:
256
+
257
+ return [k for k in self.__dict__
258
+ if k.islower() and not k.startswith("_")]
259
+
260
+ def to_columns(self,
261
+ omit_nulls: bool) -> dict[str, Any]:
262
+
263
+ result: dict[str, Any] = {}
264
+ for key, value in self.__dict__.items():
265
+ if key.islower() and not key.startswith("_") and \
266
+ (not omit_nulls or value is not None):
267
+ result[key] = value
268
+
269
+ return result
270
+
271
+ def to_params(self,
272
+ omit_nulls: bool) -> dict[str, Any]:
273
+
274
+ result: dict[str, Any] = {}
275
+ for key, value in self.__dict__.items():
276
+ if key.islower() and not key.startswith("_") and \
277
+ (not omit_nulls or value is not None):
278
+ attr: str = dict_get_key(source=PySob.attrs_map[self.__class__],
279
+ value=key) or key
280
+ result[attr] = value
281
+
282
+ return result
283
+
284
+ def data_to_params(self,
285
+ data: dict[str, Any],
286
+ omit_nulls: bool) -> dict[str, Any]:
287
+
288
+ result: dict[str, Any] = {}
289
+ for key, value in data.items():
290
+ if not omit_nulls or value is not None:
291
+ attr: str = dict_get_key(source=PySob.attrs_map[self.__class__],
292
+ value=key) or key
293
+ result[attr] = value
294
+
295
+ return result
296
+
297
+ # noinspection PyMethodMayBeStatic, PyUnusedLocal
298
+ def load_reference(self,
299
+ errors: list[str] | None, # noqa: ARG002
300
+ db_conn: Any | None) -> PySob | list[PySob] | None: # noqa: ARG002
301
+
302
+ # to be implemented by subclasses containing references
303
+ return None
304
+
305
+ def __load_references(self,
306
+ errors: list[str],
307
+ db_conn: Any) -> None:
308
+
309
+ for cls in self._references or []:
310
+ self.load_reference(errors=errors,
311
+ db_conn=db_conn)
312
+ if errors:
313
+ # 201: Erro na interação com o BD em {}: {}
314
+ msg = validate_format_error(201,
315
+ f"{cls._Db.TABLE}",
316
+ f"{'; '.join(errors)}")
317
+ errors.append(msg)
318
+ if self._logger:
319
+ self._logger.error(msg=msg)
320
+ break
321
+
322
+ @staticmethod
323
+ def count(errors: list[str] | None,
324
+ where_data: dict[str, Any],
325
+ db_conn: Any = None,
326
+ logger: Logger = None) -> int | None:
327
+
328
+ # obtem a classe invocante
329
+ # noinspection PyTypeChecker
330
+ cls: Type[PySob] = PySob.__get_invoking_class()
331
+
332
+ return db_count(errors=errors,
333
+ table=cls._Db.TABLE,
334
+ where_data=where_data,
335
+ connection=db_conn,
336
+ logger=logger)
337
+
338
+ @staticmethod
339
+ def retrieve(errors: list[str] | None,
340
+ where_data: dict[str, Any] = None,
341
+ references: list[Type[PySob]] = None,
342
+ required: bool = False,
343
+ db_conn: Any = None,
344
+ logger: Logger = None) -> list[Type[PySob]] | None:
345
+
346
+ # inicializa a variável de retorno
347
+ result: list[PySob] | None = None
348
+
349
+ # obtem a classe invocante
350
+ # noinspection PyTypeChecker
351
+ cls: Type[PySob] = PySob.__get_invoking_class()
352
+
353
+ errors = errors if isinstance(errors, list) else []
354
+ recs: list[tuple[int]] = db_select(errors=errors,
355
+ sel_stmt=f"SELECT {cls._Db.ID} FROM {cls._Db.TABLE}",
356
+ where_data=where_data,
357
+ min_count=1 if required else None,
358
+ connection=db_conn,
359
+ logger=logger)
360
+ if not errors:
361
+ # constrói a lista de objetos
362
+ # noinspection PyTypeChecker
363
+ objs: list[PySob] = []
364
+ for rec in recs:
365
+ # noinspection PyTypeChecker
366
+ objs.append(cls(references,
367
+ errors=errors,
368
+ load_references=isinstance(references, list) and len(references) > 0,
369
+ db_conn=db_conn,
370
+ where_data={cls._Db.ID: rec[0]},
371
+ logger=logger))
372
+ if errors:
373
+ break
374
+
375
+ if not errors:
376
+ result = objs
377
+
378
+ return result
379
+
380
+ @staticmethod
381
+ def erase(errors: list[str] | None,
382
+ where_data: dict[str, Any],
383
+ db_conn: Any = None,
384
+ logger: Logger = None) -> int | None:
385
+
386
+ # obtem a classe invocante
387
+ # noinspection PyTypeChecker
388
+ cls: Type[PySob] = PySob.__get_invoking_class()
389
+
390
+ # delete specified tuples
391
+ return db_delete(errors=errors,
392
+ delete_stmt=f"DELETE FROM {cls._Db.TABLE}",
393
+ where_data=where_data,
394
+ connection=db_conn,
395
+ logger=logger)
396
+
397
+ @staticmethod
398
+ def __get_invoking_class() -> Type[PySob]:
399
+
400
+ # obtem a função invocante
401
+ caller_frame: FrameInfo = stack()[1]
402
+ mark: str = f".{caller_frame.function}("
403
+
404
+ # obtem a classe invocante
405
+ caller_frame = stack()[2]
406
+ context: str = caller_frame.code_context[0]
407
+ pos_to: int = context.find(mark)
408
+ pos_from: int = context.rfind(" ", 0, pos_to)
409
+ mark = "." + context[pos_from+1:pos_to]
410
+
411
+ result: Type[PySob] | None = None
412
+ for typ in PySob.db_specs:
413
+ name = f"{typ.__module__}.{typ.__qualname__}"
414
+ if name.endswith(mark):
415
+ result = typ
416
+ break
417
+
418
+ return result