python-corekit 0.1.1__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- corekit/api/__init__.py +18 -3
- corekit/api/application.py +275 -0
- corekit/api/lifespan.py +233 -0
- corekit/api/middleware.py +93 -0
- corekit/api/routers.py +109 -1
- corekit/concurrency/__init__.py +2 -2
- corekit/concurrency/decorators.py +32 -5
- corekit/concurrency/thread_local.py +2 -2
- corekit/concurrency/worker.py +74 -65
- corekit/config/loader.py +42 -5
- corekit/config/settings.py +11 -1
- corekit/connections/__init__.py +7 -1
- corekit/connections/connectable.py +45 -4
- corekit/connections/redis/connection.py +53 -10
- corekit/connections/sql/__init__.py +33 -4
- corekit/connections/sql/connection.py +56 -3
- corekit/connections/sql/fields/__init__.py +2 -2
- corekit/connections/sql/fields/jsonb.py +13 -6
- corekit/connections/sql/migration/__init__.py +9 -5
- corekit/connections/sql/migration/base.py +3 -3
- corekit/connections/sql/migration/operations.py +135 -44
- corekit/connections/sql/migration/registry.py +2 -2
- corekit/connections/sql/operations/__init__.py +24 -0
- corekit/connections/sql/operations/base.py +111 -0
- corekit/connections/sql/operations/statements.py +170 -0
- corekit/connections/sql/query.py +4 -62
- corekit/connections/sql/table.py +33 -29
- corekit/crypto/__init__.py +3 -1
- corekit/crypto/constants.py +2 -2
- corekit/crypto/hasher.py +9 -4
- corekit/data/__init__.py +8 -0
- corekit/data/dataset.py +8 -2
- corekit/data/expressions/__init__.py +10 -2
- corekit/data/expressions/comparison.py +142 -123
- corekit/data/expressions/expression.py +71 -98
- corekit/data/expressions/operator.py +39 -0
- corekit/data/expressions/target.py +21 -0
- corekit/data/record.py +147 -147
- corekit/data/stats.py +162 -157
- corekit/decorators/__init__.py +2 -2
- corekit/decorators/exception_handling.py +38 -9
- corekit/docker/watchdog.py +50 -31
- corekit/etl/__init__.py +2 -1
- corekit/etl/connection.py +46 -44
- corekit/etl/extract/extractor.py +6 -13
- corekit/etl/orchestrator.py +19 -2
- corekit/etl/schemas.py +2 -2
- corekit/etl/transform/transformer.py +4 -1
- corekit/events/publisher.py +1 -1
- corekit/events/reader.py +26 -21
- corekit/events/sse.py +4 -1
- corekit/events/websocket.py +27 -13
- corekit/exceptions/__init__.py +33 -0
- corekit/exceptions/base.py +139 -10
- corekit/exceptions/enum.py +17 -0
- corekit/exceptions/types.py +6 -6
- corekit/files/__init__.py +2 -4
- corekit/files/base.py +15 -2
- corekit/files/enum.py +0 -5
- corekit/files/json.py +16 -2
- corekit/http/__init__.py +51 -0
- corekit/http/api.py +24 -0
- corekit/http/client.py +100 -73
- corekit/http/exceptions.py +140 -0
- corekit/http/response.py +50 -1
- corekit/http/status.py +89 -0
- corekit/jobs/__init__.py +26 -0
- corekit/jobs/registry.py +87 -0
- corekit/jobs/runner.py +80 -0
- corekit/jobs/task.py +173 -0
- corekit/log_monitor/models.py +8 -2
- corekit/log_monitor/service.py +77 -38
- corekit/notifications/base.py +18 -10
- corekit/observability/__init__.py +12 -3
- corekit/observability/benchmarkable.py +23 -5
- corekit/observability/loggable.py +21 -0
- corekit/observability/request_context.py +188 -0
- corekit/observability/timing/timer.py +4 -2
- corekit/registry/__init__.py +12 -7
- corekit/registry/ordered.py +86 -0
- corekit/registry/registry.py +55 -14
- corekit/schemas/__init__.py +10 -0
- corekit/schemas/enum.py +70 -49
- corekit/schemas/models/arbitrary.py +11 -11
- corekit/schemas/pydantic/fields.py +35 -35
- corekit/schemas/types.py +45 -40
- corekit/serialization/__init__.py +24 -0
- corekit/serialization/pickle_file.py +61 -0
- corekit/serialization/serializable.py +22 -2
- corekit/serialization/serializer.py +10 -3
- corekit/utils/__init__.py +59 -5
- corekit/utils/coercion.py +118 -0
- corekit/utils/collections.py +124 -0
- corekit/utils/ids.py +61 -5
- corekit/utils/payload.py +112 -0
- corekit/utils/raise_exc.py +8 -8
- corekit/utils/text.py +56 -0
- corekit/utils/time.py +74 -21
- corekit/utils/validators.py +15 -15
- corekit/utils/void.py +8 -8
- {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/METADATA +103 -97
- python_corekit-0.3.0.dist-info/RECORD +145 -0
- corekit/constants.py +0 -45
- corekit/exceptions/http/exceptions.py +0 -37
- corekit/files/pickle.py +0 -12
- python_corekit-0.1.1.dist-info/RECORD +0 -125
- {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/WHEEL +0 -0
- {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {python_corekit-0.1.1.dist-info → python_corekit-0.3.0.dist-info}/top_level.txt +0 -0
corekit/connections/sql/query.py
CHANGED
|
@@ -1,68 +1,10 @@
|
|
|
1
1
|
"""
|
|
2
|
-
|
|
2
|
+
The read operation, under its original name.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from pydantic import BaseModel, Field
|
|
8
|
-
from sqlmodel import desc as _desc
|
|
9
|
-
from sqlmodel import select
|
|
10
|
-
|
|
11
|
-
from corekit.connections.sql.table import NamedTable
|
|
5
|
+
from corekit.connections.sql.operations.statements import Select
|
|
12
6
|
|
|
13
7
|
__all__ = ["Query"]
|
|
14
8
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"""
|
|
18
|
-
Accumulates filters, ordering and a limit, then builds a select statement.
|
|
19
|
-
|
|
20
|
-
Query(table=User).where(User.age >= 18).order_by(User.name).limit(10)
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
|
-
table: type[NamedTable]
|
|
24
|
-
queries: list[Any] = Field(default_factory=list)
|
|
25
|
-
order_by_field: Any = None
|
|
26
|
-
order_by_desc: bool = False
|
|
27
|
-
limit_count: int = -1
|
|
28
|
-
|
|
29
|
-
def where(self, query: Any) -> "Query":
|
|
30
|
-
"""
|
|
31
|
-
Add a filter clause.
|
|
32
|
-
"""
|
|
33
|
-
self.queries.append(query)
|
|
34
|
-
return self
|
|
35
|
-
|
|
36
|
-
def order_by(self, field: Any, desc: bool = False) -> "Query":
|
|
37
|
-
"""
|
|
38
|
-
Order results by a field.
|
|
39
|
-
"""
|
|
40
|
-
self.order_by_field = field
|
|
41
|
-
self.order_by_desc = desc
|
|
42
|
-
return self
|
|
43
|
-
|
|
44
|
-
def limit(self, limit: int) -> "Query":
|
|
45
|
-
"""
|
|
46
|
-
Cap the number of rows returned. A non-positive value means no limit.
|
|
47
|
-
"""
|
|
48
|
-
self.limit_count = limit
|
|
49
|
-
return self
|
|
50
|
-
|
|
51
|
-
def build(self) -> Any:
|
|
52
|
-
"""
|
|
53
|
-
Produce the select statement.
|
|
54
|
-
"""
|
|
55
|
-
statement = select(self.table)
|
|
56
|
-
for query in self.queries:
|
|
57
|
-
statement = statement.where(query)
|
|
58
|
-
|
|
59
|
-
if self.order_by_field is not None:
|
|
60
|
-
order_by_field = self.order_by_field
|
|
61
|
-
if self.order_by_desc:
|
|
62
|
-
order_by_field = _desc(order_by_field)
|
|
63
|
-
statement = statement.order_by(order_by_field)
|
|
64
|
-
|
|
65
|
-
if self.limit_count > 0:
|
|
66
|
-
statement = statement.limit(self.limit_count)
|
|
67
|
-
|
|
68
|
-
return statement
|
|
9
|
+
#: ``Select`` names the statement it builds; ``Query`` is its original name.
|
|
10
|
+
Query = Select
|
corekit/connections/sql/table.py
CHANGED
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
Base table type.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import json
|
|
6
5
|
import logging
|
|
7
|
-
from typing import Any
|
|
6
|
+
from typing import Any, ClassVar
|
|
8
7
|
|
|
8
|
+
from pydantic import BaseModel
|
|
9
9
|
from sqlmodel import SQLModel
|
|
10
10
|
|
|
11
11
|
from corekit.registry import SmartRegistry
|
|
12
12
|
|
|
13
|
-
__all__ = ["NamedTable"]
|
|
13
|
+
__all__ = ["NamedTable", "TableExportItem"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TableExportItem(BaseModel):
|
|
17
|
+
"""
|
|
18
|
+
One exported row, tagged with the table it came from.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
table_name: str
|
|
22
|
+
data: dict[str, Any]
|
|
23
|
+
|
|
14
24
|
|
|
15
25
|
logger = logging.getLogger(__name__)
|
|
16
26
|
|
|
@@ -22,10 +32,18 @@ class NamedTable(SQLModel):
|
|
|
22
32
|
Subclasses register themselves by name, so a table class can be resolved
|
|
23
33
|
from a string -- useful for imports, exports and admin tooling that work
|
|
24
34
|
with table names rather than imported classes.
|
|
35
|
+
|
|
36
|
+
``table_name`` is the database table name (``__tablename__``). Use
|
|
37
|
+
``get_display_name`` for a human-readable label. Structured JSON columns
|
|
38
|
+
belong on ``JsonModelField`` / ``PydanticJSON``, not ad-hoc encode helpers.
|
|
25
39
|
"""
|
|
26
40
|
|
|
27
41
|
__registry__: SmartRegistry = SmartRegistry()
|
|
28
42
|
|
|
43
|
+
#: A human-readable name for the table. Declared as a ClassVar so pydantic
|
|
44
|
+
#: leaves a subclass's value as a plain string rather than wrapping it.
|
|
45
|
+
_table_display_name: ClassVar[str | None] = None
|
|
46
|
+
|
|
29
47
|
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
30
48
|
"""
|
|
31
49
|
Register every subclass under its class name.
|
|
@@ -50,16 +68,21 @@ class NamedTable(SQLModel):
|
|
|
50
68
|
@property
|
|
51
69
|
def table_name(self) -> str:
|
|
52
70
|
"""
|
|
53
|
-
The
|
|
71
|
+
The database table name for this row (``__tablename__``).
|
|
54
72
|
"""
|
|
55
|
-
return type(self).
|
|
73
|
+
return type(self).__tablename__
|
|
56
74
|
|
|
57
75
|
@classmethod
|
|
58
76
|
def get_display_name(cls) -> str:
|
|
59
77
|
"""
|
|
60
|
-
A human-readable name for the table.
|
|
78
|
+
A human-readable name for the table, falling back to the class name.
|
|
61
79
|
"""
|
|
62
|
-
|
|
80
|
+
name = cls._table_display_name
|
|
81
|
+
if name is not None and not isinstance(name, str):
|
|
82
|
+
# A subclass declared outside this module's ClassVar annotation
|
|
83
|
+
# gets its value wrapped, and the wrapper's repr is not a label.
|
|
84
|
+
name = getattr(name, "default", None) or str(name)
|
|
85
|
+
return name or cls.__name__
|
|
63
86
|
|
|
64
87
|
@classmethod
|
|
65
88
|
def create_default(cls, identifier: str, **kwargs: Any) -> "NamedTable":
|
|
@@ -70,27 +93,8 @@ class NamedTable(SQLModel):
|
|
|
70
93
|
logger.info(f"Creating default {cls.__name__} with id {kwargs['id']!r}")
|
|
71
94
|
return cls(**kwargs)
|
|
72
95
|
|
|
73
|
-
|
|
74
|
-
def _serialize(value: Any) -> str:
|
|
75
|
-
return json.dumps(value)
|
|
76
|
-
|
|
77
|
-
@staticmethod
|
|
78
|
-
def _deserialize(value: str) -> Any:
|
|
79
|
-
try:
|
|
80
|
-
return json.loads(value)
|
|
81
|
-
except (TypeError, ValueError):
|
|
82
|
-
logger.error(f"Unable to deserialize value: {value!r}")
|
|
83
|
-
raise
|
|
84
|
-
|
|
85
|
-
def serialize(self, values: dict[str, Any], key: str) -> dict[str, Any]:
|
|
86
|
-
"""
|
|
87
|
-
Replace ``values[key]`` with its JSON encoding.
|
|
88
|
-
"""
|
|
89
|
-
values[key] = self._serialize(values.get(key))
|
|
90
|
-
return values
|
|
91
|
-
|
|
92
|
-
def deserialize(self, key: str) -> Any:
|
|
96
|
+
def export(self) -> TableExportItem:
|
|
93
97
|
"""
|
|
94
|
-
|
|
98
|
+
This row as a portable record, tagged with its database table name.
|
|
95
99
|
"""
|
|
96
|
-
return self.
|
|
100
|
+
return TableExportItem(table_name=self.table_name, data=self.model_dump())
|
corekit/crypto/__init__.py
CHANGED
corekit/crypto/constants.py
CHANGED
|
@@ -3,5 +3,5 @@ EMPTY_HASH = ""
|
|
|
3
3
|
|
|
4
4
|
# There is deliberately no default salt. A salt shipped with a library is shared
|
|
5
5
|
# by every install, which defeats the purpose of salting. Supply one through
|
|
6
|
-
# configuration --
|
|
7
|
-
#
|
|
6
|
+
# configuration -- COREKIT_CRYPTO__SALT, or [crypto] salt in a config file --
|
|
7
|
+
# or pass salt= to Hasher.
|
corekit/crypto/hasher.py
CHANGED
|
@@ -4,22 +4,27 @@ from typing import Any
|
|
|
4
4
|
from corekit.config import get_settings
|
|
5
5
|
from corekit.crypto.constants import EMPTY_HASH, HASH_JOINER
|
|
6
6
|
from corekit.crypto.enum import SaltMethod
|
|
7
|
+
from corekit.exceptions import InternalCoreException, Retryability
|
|
7
8
|
from corekit.observability.loggable import Loggable
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class MissingSaltError(
|
|
11
|
+
class MissingSaltError(InternalCoreException):
|
|
11
12
|
"""
|
|
12
13
|
Raised when hashing is attempted without a configured salt.
|
|
13
14
|
"""
|
|
14
15
|
|
|
16
|
+
def __init__(self, message: str, *, error: str | None = None) -> None:
|
|
17
|
+
super().__init__(message, retryable=Retryability.NON_RETRYABLE, error=error)
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
class Hasher(Loggable):
|
|
17
21
|
"""
|
|
18
22
|
Salted SHA-256 hashing.
|
|
19
23
|
|
|
20
24
|
The salt has no default. Supply it explicitly, or configure it via
|
|
21
|
-
``
|
|
22
|
-
is deferred until first use so that importing this module
|
|
25
|
+
``COREKIT_CRYPTO__SALT`` or ``[crypto] salt`` in a corekit config file.
|
|
26
|
+
Resolution is deferred until first use so that importing this module
|
|
27
|
+
never fails.
|
|
23
28
|
"""
|
|
24
29
|
|
|
25
30
|
def __init__(
|
|
@@ -41,7 +46,7 @@ class Hasher(Loggable):
|
|
|
41
46
|
if not salt:
|
|
42
47
|
raise MissingSaltError(
|
|
43
48
|
"No crypto salt configured. Pass salt=... to Hasher, set "
|
|
44
|
-
"
|
|
49
|
+
"COREKIT_CRYPTO__SALT, or add salt under [crypto] in your corekit config. "
|
|
45
50
|
'Generate one with: python -c "import secrets; print(secrets.token_hex(32))"'
|
|
46
51
|
)
|
|
47
52
|
return salt
|
corekit/data/__init__.py
CHANGED
|
@@ -24,6 +24,7 @@ from corekit.data.expressions import (
|
|
|
24
24
|
And,
|
|
25
25
|
Comparison,
|
|
26
26
|
Contains,
|
|
27
|
+
Dialect,
|
|
27
28
|
Equals,
|
|
28
29
|
Expression,
|
|
29
30
|
FieldExpression,
|
|
@@ -34,7 +35,10 @@ from corekit.data.expressions import (
|
|
|
34
35
|
LessThanOrEquals,
|
|
35
36
|
Not,
|
|
36
37
|
NotEquals,
|
|
38
|
+
Operator,
|
|
37
39
|
Or,
|
|
40
|
+
Target,
|
|
41
|
+
UnknownFieldError,
|
|
38
42
|
ValueExpression,
|
|
39
43
|
)
|
|
40
44
|
from corekit.data.record import BaseRecord, is_valid_key, resolve_default
|
|
@@ -74,6 +78,10 @@ __all__ = [
|
|
|
74
78
|
"NotEquals",
|
|
75
79
|
"NumericFieldStats",
|
|
76
80
|
"Or",
|
|
81
|
+
"Dialect",
|
|
82
|
+
"Operator",
|
|
83
|
+
"Target",
|
|
84
|
+
"UnknownFieldError",
|
|
77
85
|
"ValueCounts",
|
|
78
86
|
"ValueExpression",
|
|
79
87
|
"is_valid_key",
|
corekit/data/dataset.py
CHANGED
|
@@ -268,8 +268,14 @@ class Dataset:
|
|
|
268
268
|
def at(self, idx: int) -> Any:
|
|
269
269
|
return self._records[idx]
|
|
270
270
|
|
|
271
|
-
def get_record(self, record_id: Any) -> Any:
|
|
272
|
-
|
|
271
|
+
def get_record(self, record_id: Any) -> Any | None:
|
|
272
|
+
"""
|
|
273
|
+
Look up a record by id, or ``None`` if it is missing.
|
|
274
|
+
|
|
275
|
+
Matches SQL ``fetch_one_by_id``: identity lookup returns absence as
|
|
276
|
+
``None`` rather than raising.
|
|
277
|
+
"""
|
|
278
|
+
return self._id_index.get(record_id)
|
|
273
279
|
|
|
274
280
|
# -- removing whole records ----------------------------------------------
|
|
275
281
|
|
|
@@ -8,8 +8,9 @@ values or against each other::
|
|
|
8
8
|
Field("first_name") == Field("nickname")
|
|
9
9
|
(Field("age") > 20) & ~Field("name").contains("test")
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
A node evaluates against a record in Python. ``to_sqlalchemy`` is the other
|
|
12
|
+
implementation of the same predicate. Operators SQL cannot mean the same way
|
|
13
|
+
are refused there rather than compiled into a different query.
|
|
13
14
|
"""
|
|
14
15
|
|
|
15
16
|
from corekit.data.expressions.comparison import (
|
|
@@ -23,9 +24,12 @@ from corekit.data.expressions.comparison import (
|
|
|
23
24
|
LessThan,
|
|
24
25
|
LessThanOrEquals,
|
|
25
26
|
NotEquals,
|
|
27
|
+
UnknownFieldError,
|
|
26
28
|
ValueExpression,
|
|
27
29
|
)
|
|
28
30
|
from corekit.data.expressions.expression import And, Expression, Not, Or
|
|
31
|
+
from corekit.data.expressions.operator import Dialect, Operator
|
|
32
|
+
from corekit.data.expressions.target import Target
|
|
29
33
|
|
|
30
34
|
__all__ = [
|
|
31
35
|
"And",
|
|
@@ -41,6 +45,10 @@ __all__ = [
|
|
|
41
45
|
"LessThanOrEquals",
|
|
42
46
|
"Not",
|
|
43
47
|
"NotEquals",
|
|
48
|
+
"UnknownFieldError",
|
|
44
49
|
"Or",
|
|
50
|
+
"Dialect",
|
|
51
|
+
"Operator",
|
|
52
|
+
"Target",
|
|
45
53
|
"ValueExpression",
|
|
46
54
|
]
|