persistence-kit 3.6.0__tar.gz → 3.7.0__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.
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/PKG-INFO +28 -2
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/README.md +27 -1
- persistence_kit-3.7.0/persistence_kit/cache/__init__.py +15 -0
- persistence_kit-3.7.0/persistence_kit/cache/contracts.py +23 -0
- persistence_kit-3.7.0/persistence_kit/cache/factory.py +55 -0
- persistence_kit-3.7.0/persistence_kit/cache/memory.py +37 -0
- persistence_kit-3.7.0/persistence_kit/cache/mongo.py +55 -0
- persistence_kit-3.7.0/persistence_kit/cache/namespaced.py +31 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/__init__.py +2 -0
- persistence_kit-3.7.0/persistence_kit/restclient/caching.py +176 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/client.py +1 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/config.py +3 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/contracts.py +1 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/memory.py +1 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/provider.py +2 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/registry.py +19 -2
- persistence_kit-3.7.0/persistence_kit/settings/cache_settings.py +16 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/pyproject.toml +1 -1
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/LICENSE +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/api/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/api/common.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/api/error_handlers.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/api/exceptions.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/api/rate_limit.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/api/route_loader.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/authenticated_user.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/bootstrap/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/bootstrap/configuration.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/bootstrap/seeders.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/bootstrap/startup.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/contracts/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/contracts/repository.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/contracts/view_repository.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/py.typed +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/dynamodb_repo/dynamodb_mapper.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/dynamodb_repo/dynamodb_repo.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/filter_ops.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/memory_repo/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/memory_repo/memory_repo.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/mongo_repo/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/mongo_repo/mongo_mapper.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/mongo_repo/mongo_repo.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/sqlalchemy_repo/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/sqlalchemy_repo/schema_evolve.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/sqlalchemy_repo/sqlalchemy_dataclass_mapper.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/sqlalchemy_repo/sqlalchemy_engine.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/sqlalchemy_repo/sqlalchemy_repo.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/sqlalchemy_repo/table_factory.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/factory/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/factory/repository_factory.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/registry/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/registry/entity_registry.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/view/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/view/populating_repository.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/resilience/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/resilience/circuit.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/aggregate.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/api_key.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/base.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/basic.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/bearer.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/login.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/auth/oauth2.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/errors.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/factory.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/mapping.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/payload.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/populate.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/resolver.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/restclient/retry.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/factory.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/ports.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/providers/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/providers/cognito_identity_provider.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/providers/memory_security_provider.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/registration.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/token_verifiers/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/token_verifiers/cognito_jwt_verifier.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/token_verifiers/memory_jwt_verifier.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/settings/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/settings/app_settings.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/settings/constants.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/settings/parsers.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/settings/repo_settings.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/contracts.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/errors.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/factory.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/local.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/media.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/routes.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/storage/s3.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/utils/__init__.py +0 -0
- {persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/utils/upsert.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: persistence-kit
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.7.0
|
|
4
4
|
Summary: Reusable persistence and repository toolkit
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -69,6 +69,11 @@ Author: Andres Felipe Serrano Barrios
|
|
|
69
69
|
- `security/`: reusable identity provider contracts, Cognito/memory adapters, and JWT verifiers
|
|
70
70
|
- `repository/`: concrete repository implementations by backend
|
|
71
71
|
- `repository_factory/`: entity registry, repository creation, and populated view repository
|
|
72
|
+
- `resilience/`: retry policy and circuit breaker shared across the kit
|
|
73
|
+
- `restclient/`: generic REST/HTTP client (transport, pluggable auth, endpoint resolvers, DTO mapper) with optional response caching
|
|
74
|
+
- `cache/`: generic key-value cache with TTL (in-memory, Mongo, DynamoDB), selected by `CACHE_BACKEND`
|
|
75
|
+
|
|
76
|
+
See `docs/restclient_and_cache.md` for the REST client and cache.
|
|
72
77
|
|
|
73
78
|
Recommended rule:
|
|
74
79
|
|
|
@@ -167,6 +172,22 @@ from persistence_kit.repository_factory import (
|
|
|
167
172
|
provide_view_repo,
|
|
168
173
|
set_registry_initializer,
|
|
169
174
|
)
|
|
175
|
+
from persistence_kit.restclient import (
|
|
176
|
+
RestClient,
|
|
177
|
+
ServiceConfig,
|
|
178
|
+
register_rest_service,
|
|
179
|
+
get_rest_client,
|
|
180
|
+
provide_rest_client,
|
|
181
|
+
set_rest_registry_initializer,
|
|
182
|
+
decode,
|
|
183
|
+
)
|
|
184
|
+
from persistence_kit.cache import (
|
|
185
|
+
Cache,
|
|
186
|
+
InMemoryTTLCache,
|
|
187
|
+
get_cache,
|
|
188
|
+
CacheBackend,
|
|
189
|
+
CacheSettings,
|
|
190
|
+
)
|
|
170
191
|
```
|
|
171
192
|
|
|
172
193
|
Use internal paths only for implementation details, for example:
|
|
@@ -273,7 +294,10 @@ the reusable FastAPI local export route.
|
|
|
273
294
|
|
|
274
295
|
## Supported Environment Variables
|
|
275
296
|
|
|
276
|
-
- `REPO_DATABASE=memory|mongo|postgres`
|
|
297
|
+
- `REPO_DATABASE=memory|mongo|postgres|dynamodb`
|
|
298
|
+
- `CACHE_BACKEND=memory|mongo|dynamodb` (default `memory`; backend for `get_cache` / the REST client cache)
|
|
299
|
+
- `CACHE_NAMESPACE` (optional key prefix; isolates apps that share one cache, e.g. a common DynamoDB table)
|
|
300
|
+
- `REST_SERVICE_URLS` (JSON map `{"service": "base_url"}` to override registered REST base URLs)
|
|
277
301
|
- `MONGO_DSN`
|
|
278
302
|
- `MONGO_DB`
|
|
279
303
|
- `POSTGRES_USER`
|
|
@@ -281,6 +305,8 @@ the reusable FastAPI local export route.
|
|
|
281
305
|
- `POSTGRES_HOST`
|
|
282
306
|
- `POSTGRES_PORT`
|
|
283
307
|
- `POSTGRES_DB`
|
|
308
|
+
- `DYNAMODB_REGION`
|
|
309
|
+
- `DYNAMODB_TABLE_PREFIX`
|
|
284
310
|
|
|
285
311
|
## Local Development
|
|
286
312
|
|
|
@@ -25,6 +25,11 @@ Author: Andres Felipe Serrano Barrios
|
|
|
25
25
|
- `security/`: reusable identity provider contracts, Cognito/memory adapters, and JWT verifiers
|
|
26
26
|
- `repository/`: concrete repository implementations by backend
|
|
27
27
|
- `repository_factory/`: entity registry, repository creation, and populated view repository
|
|
28
|
+
- `resilience/`: retry policy and circuit breaker shared across the kit
|
|
29
|
+
- `restclient/`: generic REST/HTTP client (transport, pluggable auth, endpoint resolvers, DTO mapper) with optional response caching
|
|
30
|
+
- `cache/`: generic key-value cache with TTL (in-memory, Mongo, DynamoDB), selected by `CACHE_BACKEND`
|
|
31
|
+
|
|
32
|
+
See `docs/restclient_and_cache.md` for the REST client and cache.
|
|
28
33
|
|
|
29
34
|
Recommended rule:
|
|
30
35
|
|
|
@@ -123,6 +128,22 @@ from persistence_kit.repository_factory import (
|
|
|
123
128
|
provide_view_repo,
|
|
124
129
|
set_registry_initializer,
|
|
125
130
|
)
|
|
131
|
+
from persistence_kit.restclient import (
|
|
132
|
+
RestClient,
|
|
133
|
+
ServiceConfig,
|
|
134
|
+
register_rest_service,
|
|
135
|
+
get_rest_client,
|
|
136
|
+
provide_rest_client,
|
|
137
|
+
set_rest_registry_initializer,
|
|
138
|
+
decode,
|
|
139
|
+
)
|
|
140
|
+
from persistence_kit.cache import (
|
|
141
|
+
Cache,
|
|
142
|
+
InMemoryTTLCache,
|
|
143
|
+
get_cache,
|
|
144
|
+
CacheBackend,
|
|
145
|
+
CacheSettings,
|
|
146
|
+
)
|
|
126
147
|
```
|
|
127
148
|
|
|
128
149
|
Use internal paths only for implementation details, for example:
|
|
@@ -229,7 +250,10 @@ the reusable FastAPI local export route.
|
|
|
229
250
|
|
|
230
251
|
## Supported Environment Variables
|
|
231
252
|
|
|
232
|
-
- `REPO_DATABASE=memory|mongo|postgres`
|
|
253
|
+
- `REPO_DATABASE=memory|mongo|postgres|dynamodb`
|
|
254
|
+
- `CACHE_BACKEND=memory|mongo|dynamodb` (default `memory`; backend for `get_cache` / the REST client cache)
|
|
255
|
+
- `CACHE_NAMESPACE` (optional key prefix; isolates apps that share one cache, e.g. a common DynamoDB table)
|
|
256
|
+
- `REST_SERVICE_URLS` (JSON map `{"service": "base_url"}` to override registered REST base URLs)
|
|
233
257
|
- `MONGO_DSN`
|
|
234
258
|
- `MONGO_DB`
|
|
235
259
|
- `POSTGRES_USER`
|
|
@@ -237,6 +261,8 @@ the reusable FastAPI local export route.
|
|
|
237
261
|
- `POSTGRES_HOST`
|
|
238
262
|
- `POSTGRES_PORT`
|
|
239
263
|
- `POSTGRES_DB`
|
|
264
|
+
- `DYNAMODB_REGION`
|
|
265
|
+
- `DYNAMODB_TABLE_PREFIX`
|
|
240
266
|
|
|
241
267
|
## Local Development
|
|
242
268
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from persistence_kit.cache.contracts import Cache
|
|
2
|
+
from persistence_kit.cache.factory import get_cache, reset_cache
|
|
3
|
+
from persistence_kit.cache.memory import InMemoryTTLCache
|
|
4
|
+
from persistence_kit.cache.namespaced import NamespacedCache
|
|
5
|
+
from persistence_kit.settings.cache_settings import CacheBackend, CacheSettings
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"Cache",
|
|
9
|
+
"InMemoryTTLCache",
|
|
10
|
+
"NamespacedCache",
|
|
11
|
+
"get_cache",
|
|
12
|
+
"reset_cache",
|
|
13
|
+
"CacheBackend",
|
|
14
|
+
"CacheSettings",
|
|
15
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol, runtime_checkable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@runtime_checkable
|
|
7
|
+
class Cache(Protocol):
|
|
8
|
+
"""Almacen clave-valor con expiracion opcional (TTL).
|
|
9
|
+
|
|
10
|
+
Los backends (memoria, Mongo, Dynamo) son intercambiables; el valor debe ser
|
|
11
|
+
serializable a JSON para los backends persistentes.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
async def get(self, key: str) -> Any | None: ...
|
|
15
|
+
|
|
16
|
+
async def set(self, key: str, value: Any, ttl_seconds: float | None = None) -> None: ...
|
|
17
|
+
|
|
18
|
+
async def delete(self, key: str) -> None: ...
|
|
19
|
+
|
|
20
|
+
async def clear(self, prefix: str = "") -> int:
|
|
21
|
+
"""Borra todas las claves que empiezan con ``prefix`` (o todo si vacio).
|
|
22
|
+
Devuelve cuantas borro."""
|
|
23
|
+
...
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache
|
|
4
|
+
|
|
5
|
+
from persistence_kit.cache.contracts import Cache
|
|
6
|
+
from persistence_kit.settings.cache_settings import CacheBackend, CacheSettings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@lru_cache
|
|
10
|
+
def _mongo_collection(dsn: str, dbname: str, name: str):
|
|
11
|
+
from motor.motor_asyncio import AsyncIOMotorClient
|
|
12
|
+
|
|
13
|
+
return AsyncIOMotorClient(dsn, uuidRepresentation="standard")[dbname][name]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@lru_cache
|
|
17
|
+
def _cache_cached(name: str, backend: CacheBackend) -> Cache:
|
|
18
|
+
if backend is CacheBackend.MEMORY:
|
|
19
|
+
from persistence_kit.cache.memory import InMemoryTTLCache
|
|
20
|
+
|
|
21
|
+
return InMemoryTTLCache()
|
|
22
|
+
|
|
23
|
+
if backend is CacheBackend.MONGO:
|
|
24
|
+
from persistence_kit.cache.mongo import MongoCache
|
|
25
|
+
from persistence_kit.settings.repo_settings import RepoSettings
|
|
26
|
+
|
|
27
|
+
repo = RepoSettings()
|
|
28
|
+
collection = _mongo_collection(repo.mongo_dsn, repo.mongo_db, f"cache_{name}")
|
|
29
|
+
return MongoCache(collection)
|
|
30
|
+
|
|
31
|
+
if backend is CacheBackend.DYNAMODB:
|
|
32
|
+
raise NotImplementedError("DynamoCache pendiente (paso 2 del plan de cache).")
|
|
33
|
+
|
|
34
|
+
raise ValueError(f"Cache backend no soportado: {backend}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_cache(name: str = "default") -> Cache:
|
|
38
|
+
"""Devuelve el cache para ``name``, con el backend elegido por el setting
|
|
39
|
+
``CACHE_BACKEND`` (gemelo de ``get_repo`` con ``REPO_DATABASE``).
|
|
40
|
+
|
|
41
|
+
Si ``CACHE_NAMESPACE`` esta seteado, todas las claves se prefijan con el, para
|
|
42
|
+
aislar apps que comparten un mismo cache (p. ej. una tabla DynamoDB comun).
|
|
43
|
+
"""
|
|
44
|
+
settings = CacheSettings()
|
|
45
|
+
cache = _cache_cached(name, settings.cache_backend)
|
|
46
|
+
if settings.cache_namespace:
|
|
47
|
+
from persistence_kit.cache.namespaced import NamespacedCache
|
|
48
|
+
|
|
49
|
+
return NamespacedCache(cache, settings.cache_namespace)
|
|
50
|
+
return cache
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def reset_cache() -> None:
|
|
54
|
+
_cache_cached.cache_clear()
|
|
55
|
+
_mongo_collection.cache_clear()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class InMemoryTTLCache:
|
|
8
|
+
"""Cache en memoria del proceso con expiracion perezosa por TTL.
|
|
9
|
+
|
|
10
|
+
Es por-proceso: cada instancia/tarea tiene la suya y se pierde al reiniciar.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self._store: dict[str, tuple[Any, float | None]] = {}
|
|
15
|
+
|
|
16
|
+
async def get(self, key: str) -> Any | None:
|
|
17
|
+
entry = self._store.get(key)
|
|
18
|
+
if entry is None:
|
|
19
|
+
return None
|
|
20
|
+
value, expires_at = entry
|
|
21
|
+
if expires_at is not None and time.monotonic() >= expires_at:
|
|
22
|
+
self._store.pop(key, None)
|
|
23
|
+
return None
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
async def set(self, key: str, value: Any, ttl_seconds: float | None = None) -> None:
|
|
27
|
+
expires_at = time.monotonic() + ttl_seconds if ttl_seconds else None
|
|
28
|
+
self._store[key] = (value, expires_at)
|
|
29
|
+
|
|
30
|
+
async def delete(self, key: str) -> None:
|
|
31
|
+
self._store.pop(key, None)
|
|
32
|
+
|
|
33
|
+
async def clear(self, prefix: str = "") -> int:
|
|
34
|
+
keys = [key for key in self._store if key.startswith(prefix)]
|
|
35
|
+
for key in keys:
|
|
36
|
+
self._store.pop(key, None)
|
|
37
|
+
return len(keys)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime, timedelta, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from motor.motor_asyncio import AsyncIOMotorCollection
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class MongoCache:
|
|
11
|
+
"""Cache respaldado por una coleccion Mongo con indice TTL nativo sobre
|
|
12
|
+
``expiresAt`` (el servidor borra los documentos expirados solo).
|
|
13
|
+
|
|
14
|
+
El monitor TTL de Mongo corre cada ~60s, por eso ``get`` valida la expiracion
|
|
15
|
+
ademas del indice, para no devolver un documento recien vencido.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self, collection: AsyncIOMotorCollection) -> None:
|
|
19
|
+
self._col = collection
|
|
20
|
+
self._index_ready = False
|
|
21
|
+
|
|
22
|
+
async def _ensure_index(self) -> None:
|
|
23
|
+
if not self._index_ready:
|
|
24
|
+
await self._col.create_index("expiresAt", expireAfterSeconds=0)
|
|
25
|
+
self._index_ready = True
|
|
26
|
+
|
|
27
|
+
async def get(self, key: str) -> Any | None:
|
|
28
|
+
doc = await self._col.find_one({"_id": key})
|
|
29
|
+
if doc is None:
|
|
30
|
+
return None
|
|
31
|
+
expires_at = doc.get("expiresAt")
|
|
32
|
+
if expires_at is not None:
|
|
33
|
+
if expires_at.tzinfo is None:
|
|
34
|
+
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
|
35
|
+
if expires_at <= datetime.now(timezone.utc):
|
|
36
|
+
return None
|
|
37
|
+
return doc.get("value")
|
|
38
|
+
|
|
39
|
+
async def set(self, key: str, value: Any, ttl_seconds: float | None = None) -> None:
|
|
40
|
+
await self._ensure_index()
|
|
41
|
+
doc: dict[str, Any] = {"_id": key, "value": value}
|
|
42
|
+
if ttl_seconds:
|
|
43
|
+
doc["expiresAt"] = datetime.now(timezone.utc) + timedelta(seconds=ttl_seconds)
|
|
44
|
+
await self._col.replace_one({"_id": key}, doc, upsert=True)
|
|
45
|
+
|
|
46
|
+
async def delete(self, key: str) -> None:
|
|
47
|
+
await self._col.delete_one({"_id": key})
|
|
48
|
+
|
|
49
|
+
async def clear(self, prefix: str = "") -> int:
|
|
50
|
+
if prefix:
|
|
51
|
+
query = {"_id": {"$regex": f"^{re.escape(prefix)}"}}
|
|
52
|
+
else:
|
|
53
|
+
query = {}
|
|
54
|
+
result = await self._col.delete_many(query)
|
|
55
|
+
return result.deleted_count
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from persistence_kit.cache.contracts import Cache
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NamespacedCache:
|
|
9
|
+
"""Prefija todas las claves con ``namespace:`` para aislar aplicaciones que
|
|
10
|
+
comparten un mismo cache.
|
|
11
|
+
|
|
12
|
+
Util cuando varias apps (p. ej. store_manager y siga) usan la misma tabla
|
|
13
|
+
DynamoDB o la misma coleccion Mongo: cada app pone su ``CACHE_NAMESPACE`` y
|
|
14
|
+
sus claves no colisionan.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, inner: Cache, namespace: str) -> None:
|
|
18
|
+
self._inner = inner
|
|
19
|
+
self._prefix = f"{namespace}:"
|
|
20
|
+
|
|
21
|
+
async def get(self, key: str) -> Any | None:
|
|
22
|
+
return await self._inner.get(self._prefix + key)
|
|
23
|
+
|
|
24
|
+
async def set(self, key: str, value: Any, ttl_seconds: float | None = None) -> None:
|
|
25
|
+
await self._inner.set(self._prefix + key, value, ttl_seconds)
|
|
26
|
+
|
|
27
|
+
async def delete(self, key: str) -> None:
|
|
28
|
+
await self._inner.delete(self._prefix + key)
|
|
29
|
+
|
|
30
|
+
async def clear(self, prefix: str = "") -> int:
|
|
31
|
+
return await self._inner.clear(self._prefix + prefix)
|
|
@@ -58,8 +58,10 @@ from persistence_kit.restclient.resolver import (
|
|
|
58
58
|
StaticEndpointResolver,
|
|
59
59
|
)
|
|
60
60
|
from persistence_kit.restclient.retry import RetryPolicy
|
|
61
|
+
from persistence_kit.restclient.caching import CachingRestClient
|
|
61
62
|
|
|
62
63
|
__all__ = [
|
|
64
|
+
"CachingRestClient",
|
|
63
65
|
"RestClient",
|
|
64
66
|
"RestRequest",
|
|
65
67
|
"RestResponse",
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import base64
|
|
5
|
+
import hashlib
|
|
6
|
+
import json as _json
|
|
7
|
+
import logging
|
|
8
|
+
import time
|
|
9
|
+
from typing import Any, Awaitable, Callable, Mapping
|
|
10
|
+
|
|
11
|
+
from persistence_kit.cache.contracts import Cache
|
|
12
|
+
from persistence_kit.restclient.contracts import RestClient, RestResponse
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
OnChange = Callable[[str], None]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _serialize(response: RestResponse) -> dict[str, Any]:
|
|
20
|
+
return {
|
|
21
|
+
"status_code": response.status_code,
|
|
22
|
+
"headers": dict(response.headers),
|
|
23
|
+
"content_b64": base64.b64encode(response.content).decode("ascii"),
|
|
24
|
+
"url": response.url,
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _deserialize(data: Mapping[str, Any]) -> RestResponse:
|
|
29
|
+
return RestResponse(
|
|
30
|
+
status_code=data["status_code"],
|
|
31
|
+
headers=dict(data["headers"]),
|
|
32
|
+
content=base64.b64decode(data["content_b64"]),
|
|
33
|
+
url=data["url"],
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CachingRestClient:
|
|
38
|
+
"""Decora un RestClient con cache de respuestas idempotentes (GET) y
|
|
39
|
+
deteccion de cambios por hash de contenido.
|
|
40
|
+
|
|
41
|
+
La cacheabilidad se controla por la config del servicio
|
|
42
|
+
(``cache_ttl_seconds``) o por-llamada con el parametro ``cache_ttl`` de
|
|
43
|
+
``request``: ``None`` usa el default del servicio, ``0`` no cachea esa
|
|
44
|
+
llamada (bypass), y un valor ``> 0`` cachea con ese TTL.
|
|
45
|
+
|
|
46
|
+
Cada entry guarda ``{response, hash, stored_at}``. Dentro del TTL fresco se
|
|
47
|
+
sirve del cache; dentro de la ventana ``stale-while-revalidate`` se sirve el
|
|
48
|
+
valor viejo Y se dispara una revalidacion en background (sin bloquear, sin
|
|
49
|
+
jobs): re-consulta, compara el hash y, si cambio, actualiza y llama
|
|
50
|
+
``on_change(key)``.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
inner: RestClient,
|
|
56
|
+
cache: Cache,
|
|
57
|
+
*,
|
|
58
|
+
default_ttl_seconds: float | None = None,
|
|
59
|
+
default_swr_seconds: float = 0.0,
|
|
60
|
+
name: str = "",
|
|
61
|
+
on_change: OnChange | None = None,
|
|
62
|
+
cacheable_methods: tuple[str, ...] = ("GET",),
|
|
63
|
+
) -> None:
|
|
64
|
+
self._inner = inner
|
|
65
|
+
self._cache = cache
|
|
66
|
+
self._default_ttl = default_ttl_seconds
|
|
67
|
+
self._default_swr = default_swr_seconds
|
|
68
|
+
self._name = name
|
|
69
|
+
self._on_change = on_change
|
|
70
|
+
self._methods = frozenset(method.upper() for method in cacheable_methods)
|
|
71
|
+
self._inflight: set[str] = set()
|
|
72
|
+
|
|
73
|
+
def _key(self, method: str, service: str, params: Mapping[str, Any] | None) -> str:
|
|
74
|
+
params_repr = _json.dumps(params or {}, sort_keys=True, default=str)
|
|
75
|
+
return f"{self._name}|{method}|{service}|{params_repr}"
|
|
76
|
+
|
|
77
|
+
async def request(
|
|
78
|
+
self,
|
|
79
|
+
method: str,
|
|
80
|
+
service: str,
|
|
81
|
+
*,
|
|
82
|
+
params: Mapping[str, Any] | None = None,
|
|
83
|
+
json: Any | None = None,
|
|
84
|
+
xml: Any | None = None,
|
|
85
|
+
content: bytes | None = None,
|
|
86
|
+
content_type: str | None = None,
|
|
87
|
+
soap_action: str | None = None,
|
|
88
|
+
headers: Mapping[str, str] | None = None,
|
|
89
|
+
cache_ttl: float | None = None,
|
|
90
|
+
) -> RestResponse:
|
|
91
|
+
fresh = cache_ttl if cache_ttl is not None else self._default_ttl
|
|
92
|
+
swr = self._default_swr
|
|
93
|
+
|
|
94
|
+
cacheable = (
|
|
95
|
+
method.upper() in self._methods
|
|
96
|
+
and json is None
|
|
97
|
+
and xml is None
|
|
98
|
+
and content is None
|
|
99
|
+
and fresh is not None
|
|
100
|
+
and fresh > 0
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
async def _fetch() -> RestResponse:
|
|
104
|
+
return await self._inner.request(
|
|
105
|
+
method,
|
|
106
|
+
service,
|
|
107
|
+
params=params,
|
|
108
|
+
json=json,
|
|
109
|
+
xml=xml,
|
|
110
|
+
content=content,
|
|
111
|
+
content_type=content_type,
|
|
112
|
+
soap_action=soap_action,
|
|
113
|
+
headers=headers,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if not cacheable:
|
|
117
|
+
return await _fetch()
|
|
118
|
+
|
|
119
|
+
key = self._key(method.upper(), service, params)
|
|
120
|
+
entry = await self._cache.get(key)
|
|
121
|
+
now = time.time()
|
|
122
|
+
|
|
123
|
+
if entry is not None:
|
|
124
|
+
age = now - entry.get("stored_at", now)
|
|
125
|
+
if age < fresh:
|
|
126
|
+
return _deserialize(entry["response"])
|
|
127
|
+
if swr and age < fresh + swr:
|
|
128
|
+
self._schedule_revalidate(key, entry.get("hash"), _fetch, fresh, swr)
|
|
129
|
+
return _deserialize(entry["response"])
|
|
130
|
+
|
|
131
|
+
response = await _fetch()
|
|
132
|
+
if response.is_success:
|
|
133
|
+
await self._store(key, response, fresh, swr, now)
|
|
134
|
+
return response
|
|
135
|
+
|
|
136
|
+
async def _store(
|
|
137
|
+
self, key: str, response: RestResponse, fresh: float, swr: float, now: float
|
|
138
|
+
) -> None:
|
|
139
|
+
value = {
|
|
140
|
+
"response": _serialize(response),
|
|
141
|
+
"hash": hashlib.sha256(response.content).hexdigest(),
|
|
142
|
+
"stored_at": now,
|
|
143
|
+
}
|
|
144
|
+
await self._cache.set(key, value, fresh + (swr or 0.0))
|
|
145
|
+
|
|
146
|
+
def _schedule_revalidate(
|
|
147
|
+
self,
|
|
148
|
+
key: str,
|
|
149
|
+
old_hash: str | None,
|
|
150
|
+
fetch: Callable[[], Awaitable[RestResponse]],
|
|
151
|
+
fresh: float,
|
|
152
|
+
swr: float,
|
|
153
|
+
) -> None:
|
|
154
|
+
if key in self._inflight:
|
|
155
|
+
return
|
|
156
|
+
self._inflight.add(key)
|
|
157
|
+
|
|
158
|
+
async def _run() -> None:
|
|
159
|
+
try:
|
|
160
|
+
response = await fetch()
|
|
161
|
+
if response.is_success:
|
|
162
|
+
new_hash = hashlib.sha256(response.content).hexdigest()
|
|
163
|
+
await self._store(key, response, fresh, swr, time.time())
|
|
164
|
+
if new_hash != old_hash:
|
|
165
|
+
logger.info("cache: cambio detectado en %s", key)
|
|
166
|
+
if self._on_change is not None:
|
|
167
|
+
self._on_change(key)
|
|
168
|
+
except Exception:
|
|
169
|
+
logger.exception("cache: fallo revalidando %s", key)
|
|
170
|
+
finally:
|
|
171
|
+
self._inflight.discard(key)
|
|
172
|
+
|
|
173
|
+
asyncio.create_task(_run())
|
|
174
|
+
|
|
175
|
+
async def aclose(self) -> None:
|
|
176
|
+
await self._inner.aclose()
|
|
@@ -72,6 +72,7 @@ class HttpxRestClient(ModelMappingMixin):
|
|
|
72
72
|
content_type: str | None = None,
|
|
73
73
|
soap_action: str | None = None,
|
|
74
74
|
headers: Mapping[str, str] | None = None,
|
|
75
|
+
cache_ttl: float | None = None,
|
|
75
76
|
) -> RestResponse:
|
|
76
77
|
url = await self._resolver.resolve(service)
|
|
77
78
|
request = prepare_request(
|
|
@@ -15,6 +15,9 @@ class ServiceConfig(BaseModel):
|
|
|
15
15
|
raise_for_status: bool = True
|
|
16
16
|
default_headers: dict[str, str] = Field(default_factory=dict)
|
|
17
17
|
user_agent: str | None = None
|
|
18
|
+
cacheable: bool = False
|
|
19
|
+
cache_ttl_seconds: float | None = None
|
|
20
|
+
cache_stale_while_revalidate_seconds: float = 0.0
|
|
18
21
|
|
|
19
22
|
@classmethod
|
|
20
23
|
def from_settings(cls, settings: Any, **overrides: Any) -> "ServiceConfig":
|
|
@@ -82,6 +82,7 @@ class MemoryRestClient(ModelMappingMixin):
|
|
|
82
82
|
content_type: str | None = None,
|
|
83
83
|
soap_action: str | None = None,
|
|
84
84
|
headers: Mapping[str, str] | None = None,
|
|
85
|
+
cache_ttl: float | None = None,
|
|
85
86
|
) -> RestResponse:
|
|
86
87
|
url = await self._resolver.resolve(service) if self._resolver else service
|
|
87
88
|
request = prepare_request(
|
|
@@ -46,6 +46,7 @@ def register_rest_service(
|
|
|
46
46
|
config: ServiceConfig | None = None,
|
|
47
47
|
retry_policy: RetryPolicy | None = None,
|
|
48
48
|
circuit_breaker: CircuitBreaker | None = None,
|
|
49
|
+
on_cache_change: Callable[[str], None] | None = None,
|
|
49
50
|
replace: bool = False,
|
|
50
51
|
) -> None:
|
|
51
52
|
default_rest_registry().register(
|
|
@@ -56,6 +57,7 @@ def register_rest_service(
|
|
|
56
57
|
config=config,
|
|
57
58
|
retry_policy=retry_policy,
|
|
58
59
|
circuit_breaker=circuit_breaker,
|
|
60
|
+
on_cache_change=on_cache_change,
|
|
59
61
|
replace=replace,
|
|
60
62
|
)
|
|
61
63
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any
|
|
4
|
+
from typing import Any, Callable
|
|
5
5
|
|
|
6
6
|
from persistence_kit.resilience import CircuitBreaker
|
|
7
7
|
from persistence_kit.restclient.auth.base import NoAuth
|
|
@@ -24,6 +24,7 @@ class RegisteredService:
|
|
|
24
24
|
authenticator: Authenticator
|
|
25
25
|
retry_policy: RetryPolicy | None = None
|
|
26
26
|
circuit_breaker: CircuitBreaker | None = None
|
|
27
|
+
on_cache_change: Callable[[str], None] | None = None
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class RestClientRegistry:
|
|
@@ -59,6 +60,7 @@ class RestClientRegistry:
|
|
|
59
60
|
config: ServiceConfig | None = None,
|
|
60
61
|
retry_policy: RetryPolicy | None = None,
|
|
61
62
|
circuit_breaker: CircuitBreaker | None = None,
|
|
63
|
+
on_cache_change: Callable[[str], None] | None = None,
|
|
62
64
|
replace: bool = False,
|
|
63
65
|
) -> "RestClientRegistry":
|
|
64
66
|
if name in self._services and not replace:
|
|
@@ -70,6 +72,7 @@ class RestClientRegistry:
|
|
|
70
72
|
authenticator=authenticator or NoAuth(),
|
|
71
73
|
retry_policy=retry_policy,
|
|
72
74
|
circuit_breaker=circuit_breaker,
|
|
75
|
+
on_cache_change=on_cache_change,
|
|
73
76
|
)
|
|
74
77
|
self._clients.pop(name, None)
|
|
75
78
|
return self
|
|
@@ -90,13 +93,27 @@ class RestClientRegistry:
|
|
|
90
93
|
"Instala con `persistence-kit[restclient]`."
|
|
91
94
|
) from exc
|
|
92
95
|
|
|
93
|
-
client = HttpxRestClient(
|
|
96
|
+
client: RestClient = HttpxRestClient(
|
|
94
97
|
config=service.config,
|
|
95
98
|
resolver=service.resolver,
|
|
96
99
|
authenticator=service.authenticator,
|
|
97
100
|
retry_policy=service.retry_policy,
|
|
98
101
|
circuit_breaker=service.circuit_breaker,
|
|
99
102
|
)
|
|
103
|
+
|
|
104
|
+
if service.config.cache_ttl_seconds or service.config.cacheable:
|
|
105
|
+
from persistence_kit.cache.factory import get_cache
|
|
106
|
+
from persistence_kit.restclient.caching import CachingRestClient
|
|
107
|
+
|
|
108
|
+
client = CachingRestClient(
|
|
109
|
+
client,
|
|
110
|
+
get_cache("restclient"),
|
|
111
|
+
default_ttl_seconds=service.config.cache_ttl_seconds,
|
|
112
|
+
default_swr_seconds=service.config.cache_stale_while_revalidate_seconds,
|
|
113
|
+
name=name,
|
|
114
|
+
on_change=service.on_cache_change,
|
|
115
|
+
)
|
|
116
|
+
|
|
100
117
|
self._clients[name] = client
|
|
101
118
|
return client
|
|
102
119
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CacheBackend(str, Enum):
|
|
7
|
+
MEMORY = "memory"
|
|
8
|
+
MONGO = "mongo"
|
|
9
|
+
DYNAMODB = "dynamodb"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class CacheSettings(BaseSettings):
|
|
13
|
+
cache_backend: CacheBackend = CacheBackend.MEMORY
|
|
14
|
+
cache_namespace: str = ""
|
|
15
|
+
|
|
16
|
+
model_config = SettingsConfigDict(env_file=".env", extra="ignore")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/contracts/view_repository.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/memory_repo/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/mongo_repo/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository/mongo_repo/mongo_repo.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/repository_factory/view/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/providers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{persistence_kit-3.6.0 → persistence_kit-3.7.0}/persistence_kit/security/token_verifiers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|