python-neva 2.2.1__py3-none-any.whl → 2.4.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.
- neva/arch/application.py +6 -2
- neva/config/repository.py +8 -2
- neva/database/manager.py +2 -0
- neva/database/provider.py +1 -2
- neva/obs/instrumentation/__init__.py +1 -0
- neva/obs/instrumentation/sqlalchemy.py +15 -0
- neva/security/encryption/encrypter.py +10 -13
- neva/security/hashing/hash_manager.py +23 -31
- {python_neva-2.2.1.dist-info → python_neva-2.4.0.dist-info}/METADATA +2 -1
- {python_neva-2.2.1.dist-info → python_neva-2.4.0.dist-info}/RECORD +11 -9
- {python_neva-2.2.1.dist-info → python_neva-2.4.0.dist-info}/WHEEL +0 -0
neva/arch/application.py
CHANGED
|
@@ -52,8 +52,12 @@ class Application:
|
|
|
52
52
|
|
|
53
53
|
self.bind(lambda: self.config, interface=ConfigRepository)
|
|
54
54
|
|
|
55
|
-
providers_from_file = self.config.get(
|
|
56
|
-
|
|
55
|
+
providers_from_file: list[type[ServiceProvider]] = self.config.get(
|
|
56
|
+
"providers.providers", type_=list[type[ServiceProvider]]
|
|
57
|
+
).unwrap_or([])
|
|
58
|
+
providers_from_app = self.config.get(
|
|
59
|
+
"app.providers", type_=list[type[ServiceProvider]]
|
|
60
|
+
).unwrap_or([])
|
|
57
61
|
providers: set[type[ServiceProvider]] = set(providers_from_file).union(
|
|
58
62
|
set(providers_from_app)
|
|
59
63
|
)
|
neva/config/repository.py
CHANGED
|
@@ -60,12 +60,18 @@ class ConfigRepository:
|
|
|
60
60
|
current[keys[-1]] = value
|
|
61
61
|
return Ok(None)
|
|
62
62
|
|
|
63
|
-
def get
|
|
63
|
+
def get[T](
|
|
64
|
+
self,
|
|
65
|
+
key: str,
|
|
66
|
+
default: T | None = None,
|
|
67
|
+
type_: type[T] | None = None,
|
|
68
|
+
) -> Result[T, str]:
|
|
64
69
|
"""Get a configuration value using dot notation.
|
|
65
70
|
|
|
66
71
|
Args:
|
|
67
72
|
key: Dot-notated key path (e.g., "database.host").
|
|
68
73
|
default: Default value to return if key is not found.
|
|
74
|
+
type_: Type to cast the value to.
|
|
69
75
|
|
|
70
76
|
Returns:
|
|
71
77
|
Result containing the configuration value or the default.
|
|
@@ -81,7 +87,7 @@ class ConfigRepository:
|
|
|
81
87
|
return Ok(default)
|
|
82
88
|
return Err(f"Config key '{key}' not found")
|
|
83
89
|
current = current[k]
|
|
84
|
-
return Ok(current)
|
|
90
|
+
return Ok(current) # type: ignore [arg-type]
|
|
85
91
|
except KeyError:
|
|
86
92
|
if default is not None:
|
|
87
93
|
return Ok(default)
|
neva/database/manager.py
CHANGED
|
@@ -10,6 +10,7 @@ from neva import Nothing, Option, Some
|
|
|
10
10
|
from neva.database.connection import ConnectionManager, TransactionContext
|
|
11
11
|
from neva.database.transaction import BoundTransaction, Transaction
|
|
12
12
|
from neva.obs import LogManager
|
|
13
|
+
from neva.obs.instrumentation.sqlalchemy import instrument
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
@final
|
|
@@ -30,6 +31,7 @@ class DatabaseManager:
|
|
|
30
31
|
name: The connection name.
|
|
31
32
|
engine: The async engine.
|
|
32
33
|
"""
|
|
34
|
+
instrument(engine)
|
|
33
35
|
self._engines[name] = engine
|
|
34
36
|
self._session_factories[name] = async_sessionmaker(
|
|
35
37
|
bind=engine,
|
neva/database/provider.py
CHANGED
|
@@ -8,7 +8,6 @@ from sqlalchemy.ext.asyncio import create_async_engine
|
|
|
8
8
|
|
|
9
9
|
from neva import Err, Ok, Result
|
|
10
10
|
from neva.arch import ServiceProvider
|
|
11
|
-
from neva.config.repository import ConfigRepository
|
|
12
11
|
from neva.database.connection import TransactionContext
|
|
13
12
|
from neva.database.manager import DatabaseManager
|
|
14
13
|
from neva.obs import LogManager
|
|
@@ -29,7 +28,7 @@ class DatabaseServiceProvider(ServiceProvider):
|
|
|
29
28
|
logger: LogManager = self.app.make(LogManager).unwrap()
|
|
30
29
|
db: DatabaseManager = self.app.make(DatabaseManager).unwrap()
|
|
31
30
|
logger.info("Beginning SQLAlchemy initialization...")
|
|
32
|
-
match self.app.
|
|
31
|
+
match self.app.config.get("database"):
|
|
33
32
|
case Ok(config):
|
|
34
33
|
connections: dict = config.get("connections", {})
|
|
35
34
|
for name, conn_config in connections.items():
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Various OTel instrumentation."""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""SQLAlchemy instrumentation."""
|
|
2
|
+
|
|
3
|
+
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor
|
|
4
|
+
from sqlalchemy import Engine
|
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def instrument(engine: AsyncEngine | Engine) -> None:
|
|
9
|
+
"""Instrument SQLAlchemy engine."""
|
|
10
|
+
match engine:
|
|
11
|
+
case AsyncEngine():
|
|
12
|
+
to_instrument = engine.sync_engine
|
|
13
|
+
case Engine():
|
|
14
|
+
to_instrument = engine
|
|
15
|
+
SQLAlchemyInstrumentor().instrument(engine=to_instrument)
|
|
@@ -10,7 +10,6 @@ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
|
|
10
10
|
|
|
11
11
|
from neva import Err, Ok, Result
|
|
12
12
|
from neva.arch import Application
|
|
13
|
-
from neva.config import ConfigRepository
|
|
14
13
|
from neva.security.encryption.protocol import JsonValue
|
|
15
14
|
|
|
16
15
|
|
|
@@ -127,18 +126,16 @@ class AesEncrypter:
|
|
|
127
126
|
Raises:
|
|
128
127
|
ValueError: If no encryption key is configured.
|
|
129
128
|
"""
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
ciphers = [AESGCM(self._parse_key(key))]
|
|
129
|
+
previous_keys = self._app.config.get(
|
|
130
|
+
"app.previous_keys", default=[], type_=list[str]
|
|
131
|
+
).unwrap_or([])
|
|
132
|
+
|
|
133
|
+
match self._app.config.get("app.key", type_=str):
|
|
134
|
+
case Err():
|
|
135
|
+
msg = "No encryption key configured. Set 'app.key' in configuration."
|
|
136
|
+
raise ValueError(msg)
|
|
137
|
+
case Ok(key):
|
|
138
|
+
ciphers = [AESGCM(self._parse_key(key))]
|
|
142
139
|
|
|
143
140
|
for prev_key in previous_keys:
|
|
144
141
|
ciphers.append(AESGCM(self._parse_key(prev_key)))
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"""Hash manager for managing password hashing strategies."""
|
|
2
2
|
|
|
3
|
-
from typing import override
|
|
3
|
+
from typing import Literal, cast, override
|
|
4
4
|
|
|
5
|
-
from neva import Option
|
|
5
|
+
from neva import Option
|
|
6
6
|
from neva.arch import Application
|
|
7
|
-
from neva.config import
|
|
7
|
+
from neva.security.hashing.config import Argon2Config, BcryptConfig
|
|
8
8
|
from neva.security.hashing.hashers.argon2 import Argon2Hasher
|
|
9
9
|
from neva.security.hashing.hashers.bcrypt import BcryptHasher
|
|
10
10
|
from neva.security.hashing.hashers.protocol import Hasher
|
|
@@ -32,13 +32,7 @@ class HashManager(StrategyResolver[Hasher]):
|
|
|
32
32
|
Returns:
|
|
33
33
|
Option containing the default hasher name.
|
|
34
34
|
"""
|
|
35
|
-
|
|
36
|
-
if config_result.is_err:
|
|
37
|
-
return from_optional(None)
|
|
38
|
-
|
|
39
|
-
config = config_result.unwrap()
|
|
40
|
-
driver = config.get("hashing.driver", default=None).unwrap_or(None)
|
|
41
|
-
return from_optional(driver)
|
|
35
|
+
return self.app.config.get("hashing.driver", type_=str).ok()
|
|
42
36
|
|
|
43
37
|
def make(
|
|
44
38
|
self,
|
|
@@ -113,18 +107,17 @@ class HashManager(StrategyResolver[Hasher]):
|
|
|
113
107
|
Returns:
|
|
114
108
|
Configured Argon2Hasher instance.
|
|
115
109
|
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return Argon2Hasher()
|
|
110
|
+
argon_config = manager.app.config.get(
|
|
111
|
+
"hashing.argon", default={}, type_=Argon2Config
|
|
112
|
+
).unwrap()
|
|
113
|
+
|
|
114
|
+
return Argon2Hasher(
|
|
115
|
+
time_cost=cast(int, argon_config.get("time_cost", 2)),
|
|
116
|
+
memory_cost=cast(int, argon_config.get("memory_cost", 102400)),
|
|
117
|
+
parallelism=cast(int, argon_config.get("parallelism", 8)),
|
|
118
|
+
hash_len=cast(int, argon_config.get("hash_len", 16)),
|
|
119
|
+
salt_len=cast(int, argon_config.get("salt_len", 16)),
|
|
120
|
+
)
|
|
128
121
|
|
|
129
122
|
def _create_bcrypt_hasher(self, manager: StrategyResolver[Hasher]) -> BcryptHasher:
|
|
130
123
|
"""Create an instance of the Bcrypt hash strategy.
|
|
@@ -135,12 +128,11 @@ class HashManager(StrategyResolver[Hasher]):
|
|
|
135
128
|
Returns:
|
|
136
129
|
Configured BcryptHasher instance.
|
|
137
130
|
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return BcryptHasher()
|
|
131
|
+
bcrypt_config = manager.app.config.get(
|
|
132
|
+
"hashing.bcrypt", default={}, type_=BcryptConfig
|
|
133
|
+
).unwrap()
|
|
134
|
+
|
|
135
|
+
return BcryptHasher(
|
|
136
|
+
rounds=cast(int, bcrypt_config.get("rounds", 12)),
|
|
137
|
+
prefix=cast(Literal["2a", "2b"], bcrypt_config.get("prefix", "2b")),
|
|
138
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-neva
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: aiosqlite>=0.20.0
|
|
@@ -9,6 +9,7 @@ Requires-Dist: cryptography>=46.0.3
|
|
|
9
9
|
Requires-Dist: dishka>=1.7.2
|
|
10
10
|
Requires-Dist: fastapi[all]>=0.129.0
|
|
11
11
|
Requires-Dist: faststream>=0.6.6
|
|
12
|
+
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.62b0
|
|
12
13
|
Requires-Dist: pwdlib[argon2,bcrypt]>=0.3.0
|
|
13
14
|
Requires-Dist: pyinstrument>=5.1.1
|
|
14
15
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
@@ -2,7 +2,7 @@ neva/__init__.py,sha256=Pj77_FvqTS-Xn0C1MZE34zTzl5QQcIyF1Zne64L37So,333
|
|
|
2
2
|
neva/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
neva/arch/__init__.py,sha256=ZKMZ3u-pxVRRQcbqIF2L4F5IUclRcRVNe83hnN5YYeo,470
|
|
4
4
|
neva/arch/app.py,sha256=devhJqAmssWCdhvT7d-mE6vyUuEXX5rIY06oIoR21QE,4451
|
|
5
|
-
neva/arch/application.py,sha256=
|
|
5
|
+
neva/arch/application.py,sha256=pBoN8ExT1_MXjeJhrRNdp_S3KdB7a9qf_Hc_sHNRei4,6096
|
|
6
6
|
neva/arch/config.py,sha256=R8_wMKTwX8vTFqfQhgglDQ9PfB6Yo6mx6OnKg8bnIWw,1042
|
|
7
7
|
neva/arch/facade.py,sha256=4aTOFuBwM58gn1FR7X1mOoKdi37J10OjPOpLVnCTCw8,7915
|
|
8
8
|
neva/arch/faststream.py,sha256=1eUQRh3ahmQ6T991iHpxMeItDI53mEFq_30krIagNmM,2361
|
|
@@ -10,12 +10,12 @@ neva/arch/service_provider.py,sha256=g9tvlBNpGxdJ6yL9w6L-oPLzC6AV_Z6vLDR-TW3Kqz0
|
|
|
10
10
|
neva/config/__init__.py,sha256=Ygfnh-MRRKYDdf54_LeMSHwmck5VpNHTdnnMtGEFLtU,121
|
|
11
11
|
neva/config/base_providers.py,sha256=fK4feZWLzchpQ_xaeumtR9P2JK5nuk8Vq5O7JWkGyO8,908
|
|
12
12
|
neva/config/loader.py,sha256=ZIcx8oYIi2r4wv1G_Q7_a6nG7wARyzjiZ2wF43t3B2I,3942
|
|
13
|
-
neva/config/repository.py,sha256=
|
|
13
|
+
neva/config/repository.py,sha256=RlErYZfNBQQkwId3zXs3wjgcKR-uhUxNtNjh_3r9cD4,4972
|
|
14
14
|
neva/database/__init__.py,sha256=3yYnEe8HQM86tURyAhTbMtE7AXubIEMN3GuwxkkT-bk,508
|
|
15
15
|
neva/database/config.py,sha256=bVKUqlrYDFrURqg_FbntPXyVkbOUPSVXQJTKjyucjL0,451
|
|
16
16
|
neva/database/connection.py,sha256=et1fvGxa2iHTJ8yzAv9VIYqfD4GtGaV0KQUW97SdLHQ,7107
|
|
17
|
-
neva/database/manager.py,sha256=
|
|
18
|
-
neva/database/provider.py,sha256=
|
|
17
|
+
neva/database/manager.py,sha256=tQFHj2HZzo3oxdWyWNQlv5277I7vh7vDlEEiUyAKjDs,4511
|
|
18
|
+
neva/database/provider.py,sha256=GvWZjT3f4BEzQFY9yB2QHciw2NXGoy6L-d1CbP6vPa4,1708
|
|
19
19
|
neva/database/transaction.py,sha256=yQuFDhdXipUWac3zx4rVy6R6XUB_xPT_CLoQ-26223Y,5933
|
|
20
20
|
neva/events/__init__.py,sha256=xwIAXNcOASpuCY4DJpshZc1gtxQHimzwy-s7c-4QuiQ,746
|
|
21
21
|
neva/events/dispatcher.py,sha256=3xMhxE5uQBY7648jp75NkRwu09lqYo7ymmfSvnl6kT0,2850
|
|
@@ -24,6 +24,8 @@ neva/events/event_registry.py,sha256=jucYOUjCERz-ICdPiDrxYw_ySvmPJOcRzYKCgka29qY
|
|
|
24
24
|
neva/events/listener.py,sha256=AMfi24R_WA_-mVg0fxfOrwY6EV8kO1wU5YpAFPvGrus,1825
|
|
25
25
|
neva/events/provider.py,sha256=ilPW_-zaLMrh-cgWphLe_aqvs00LNp93OOMFFpIppyE,767
|
|
26
26
|
neva/obs/__init__.py,sha256=dVzgljk9Hvo44LI34RcwbDyT42_z4nSnFVmL4GLbKD0,331
|
|
27
|
+
neva/obs/instrumentation/__init__.py,sha256=SfIAvuCs-L0kEzOxUJ4aOIkhbydCe7qBwWtGeDELVA0,36
|
|
28
|
+
neva/obs/instrumentation/sqlalchemy.py,sha256=kwk43NthFi4mPMhDJI55rxt9YPOY_mwOkr2E-cD64ZY,497
|
|
27
29
|
neva/obs/logging/__init__.py,sha256=gIYBAzMK7-wPGEpA_kYKvJPT3hYN99OcCc9UuHFeiHk,251
|
|
28
30
|
neva/obs/logging/manager.py,sha256=ktd3muBGU3e1KuBK6tSU5faRJRliPwmknuqJ2EYeFBk,2422
|
|
29
31
|
neva/obs/logging/provider.py,sha256=lt5oma5eV6mjN4C5iFhyYf0BmI9BMbFVBJJNSy2XOkg,638
|
|
@@ -33,11 +35,11 @@ neva/obs/middleware/profiler.py,sha256=ovx3s-39WpNtu2KhzVMWyIJa6OLrSVDuL56xyDA-v
|
|
|
33
35
|
neva/security/__init__.py,sha256=Dhh3ZsseP6biWgRQY4hYKkoYFCR7ehhxz4VBqN8e02k,440
|
|
34
36
|
neva/security/provider.py,sha256=20ZPKp4SxYV7pN1TN1yx-RIHken2TEHJBpaVjIFRxYI,499
|
|
35
37
|
neva/security/encryption/__init__.py,sha256=eVWW-qk03vy7Bn-sNQ2WJHWZI6tPJOkknJz8llc1m0M,288
|
|
36
|
-
neva/security/encryption/encrypter.py,sha256=
|
|
38
|
+
neva/security/encryption/encrypter.py,sha256=E2DvTsG_756L75K1IUeYlEzazKn4Eqk1psryCk7HEHE,5156
|
|
37
39
|
neva/security/encryption/protocol.py,sha256=H_JSt8IBH_AVkgccltY_4dGtcmESt4o76W0vxszBikM,839
|
|
38
40
|
neva/security/hashing/__init__.py,sha256=k3SviEvtJLjBXTkR5I6w9YMPw6Q-LIoE1wGNz3GYvOE,374
|
|
39
41
|
neva/security/hashing/config.py,sha256=ltj7_GW44gSg2N0i8CEYaw6Y6xUfzwsr4zXk5ic_sJI,624
|
|
40
|
-
neva/security/hashing/hash_manager.py,sha256=
|
|
42
|
+
neva/security/hashing/hash_manager.py,sha256=7Oy2gOUZvzRbMsD3unvE1HFDjcHvB-U3Gl84kd3Lbz4,4568
|
|
41
43
|
neva/security/hashing/hashers/__init__.py,sha256=Zn_Q2tj_8lhhwJRZ7RXeJLYujd-3j7Rr8JQO2sSGgJs,44
|
|
42
44
|
neva/security/hashing/hashers/argon2.py,sha256=pJqTl3_oCJCjJarnQwNSFRznlaijjbjdDVpC0DXDoSg,1402
|
|
43
45
|
neva/security/hashing/hashers/bcrypt.py,sha256=H_15r-z9DYkd4HEbTz0QA4NHVBiFDXEqHkngP3HMSBY,1083
|
|
@@ -72,6 +74,6 @@ neva/testing/fakes.py,sha256=ffAAZC7WymuutVYk3vefuPDUvpNHav2QV2lj-Gw0Kic,2666
|
|
|
72
74
|
neva/testing/fixtures.py,sha256=-l_Drw6nXTD_cKpfg1Z1pmsXnaD3NM2kihdRHxSkfzE,1657
|
|
73
75
|
neva/testing/http.py,sha256=9oKNzaz38nCKkL-ZUF5CRWFPqpNmXkqbYcoukC509_Q,494
|
|
74
76
|
neva/testing/test_case.py,sha256=FzXBHctkVkFNu46S5lnt7iRX91TLPLfL1Lzgn-iU-_E,3325
|
|
75
|
-
python_neva-2.
|
|
76
|
-
python_neva-2.
|
|
77
|
-
python_neva-2.
|
|
77
|
+
python_neva-2.4.0.dist-info/METADATA,sha256=XyVNgtWT8cYqCGaFuRTgviKMZTOF7aKJaGTZz8nkUWc,691
|
|
78
|
+
python_neva-2.4.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
79
|
+
python_neva-2.4.0.dist-info/RECORD,,
|
|
File without changes
|