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.
- pypomes_sob-0.0.1/.gitignore +16 -0
- pypomes_sob-0.0.1/LICENSE +21 -0
- pypomes_sob-0.0.1/PKG-INFO +18 -0
- pypomes_sob-0.0.1/README.md +2 -0
- pypomes_sob-0.0.1/pyproject.toml +28 -0
- pypomes_sob-0.0.1/src/pypomes_sob/.ruff.toml +136 -0
- pypomes_sob-0.0.1/src/pypomes_sob/__init__.py +9 -0
- pypomes_sob-0.0.1/src/pypomes_sob/sob_pomes.py +418 -0
|
@@ -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,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
|