sysnet-pyutils 1.8.0__tar.gz → 1.9.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.
- {sysnet_pyutils-1.8.0/sysnet_pyutils.egg-info → sysnet_pyutils-1.9.0}/PKG-INFO +50 -3
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/README.md +49 -2
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/pyproject.toml +1 -1
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/__init__.py +7 -0
- sysnet_pyutils-1.9.0/sysnet_pyutils/request_context.py +90 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/utils.py +175 -31
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0/sysnet_pyutils.egg-info}/PKG-INFO +50 -3
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils.egg-info/SOURCES.txt +4 -0
- sysnet_pyutils-1.9.0/tests/test_context_legacy_robustness.py +175 -0
- sysnet_pyutils-1.9.0/tests/test_context_secrets_masking.py +210 -0
- sysnet_pyutils-1.9.0/tests/test_request_context.py +301 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/LICENSE +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/setup.cfg +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/barcode.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/constants.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/cron_utils/__init__.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/cron_utils/validation.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/data_utils.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/domino.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/geo.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/globalmodel/__init__.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/globalmodel/global_model.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/globalmodel/models.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/globalmodel/timeutils.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/ident.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/log.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/models/__init__.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/models/general.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/ses.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/syslog/__init__.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/syslog/factory.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils/tools.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils.egg-info/dependency_links.txt +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils.egg-info/not-zip-safe +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils.egg-info/requires.txt +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/sysnet_pyutils.egg-info/top_level.txt +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_config_secrets.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_config_store.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_cron_utils.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_cron_validation_edge.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_data_utils.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_data_utils_datetime.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_data_utils_edge.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_data_utils_missing_branches.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_domino.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_domino_edge.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_geo.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_global_model_recursive.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_global_model_timezone.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_ident_barcode.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_log_importerror_branch.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_log_mongo.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_models_contact.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_models_general.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_models_general_edge.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_package_init_version.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_recursive_update.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_ses.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_ses_importerror_branch.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_syslog_factory.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_tools.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_utils.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_utils_edge.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_utils_log_base64.py +0 -0
- {sysnet_pyutils-1.8.0 → sysnet_pyutils-1.9.0}/tests/test_utils_missing_branches.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysnet-pyutils
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: SYSNET Python Utilities - kompletní sada nástrojů pro Python aplikace
|
|
5
5
|
Author-email: Data Developer <info@sysnet.cz>
|
|
6
6
|
Maintainer-email: SYSNET Development Team <info@sysnet.cz>
|
|
@@ -729,7 +729,7 @@ Knihovna obsahuje základní, v aplikacích hojně používané, utility pro Pyt
|
|
|
729
729
|
|
|
730
730
|
[](https://www.python.org/downloads/)
|
|
731
731
|
[](LICENSE)
|
|
732
|
-
[](https://pypi.org/project/sysnet-pyutils/1.9.0/)
|
|
733
733
|
[](https://github.com/SYSNET-CZ/pyutils/actions/workflows/ci.yml)
|
|
734
734
|
[](tests/)
|
|
735
735
|
|
|
@@ -901,6 +901,44 @@ if ctx.check_api_key('some_key'):
|
|
|
901
901
|
print(f"Přihlášen jako: {ctx.user_name}")
|
|
902
902
|
```
|
|
903
903
|
|
|
904
|
+
> **`Context` je singleton se sdíleným stavem.** `check_api_key()` ukládá identitu do jediné instance, kterou sdílejí
|
|
905
|
+
> všechny souběžné požadavky. Ve webové službě (FastAPI) může jeden požadavek po `await` vidět identitu jiného.
|
|
906
|
+
> Pokus se dvěma souběžnými požadavky to potvrdil: `Context` vrátil první osobě jméno druhé.
|
|
907
|
+
> **Ověřování API klíčů patří knihovně `sysnet-apikeys`** (standard CML `api-keys.md`); `check_api_key()` a
|
|
908
|
+
> **Ověřování API klíčů patří knihovně `sysnet-apikeys`** (standard CML `api-keys.md`); `check_api_key()`,
|
|
909
|
+
> `check_user()` a `api_keys_init()` jsou zastaralé (`DeprecationWarning`) a zůstávají jen kvůli stávajícím službám.
|
|
910
|
+
> Tajemství (`config`, `api_key`, `cert_pass`) se čtou jako dřív, ale nejsou ve `vars()`, v `repr` ani v `pickle`/`copy`;
|
|
911
|
+
> pro diagnostiku slouží `ctx.safe_dict()`.
|
|
912
|
+
|
|
913
|
+
##### Per-request kontext (`RequestContext`)
|
|
914
|
+
|
|
915
|
+
`RequestContext` jen **nese** identitu ověřenou jinde, v `ContextVar`, takže ji každý požadavek vidí jen svou.
|
|
916
|
+
Ověřuje `sysnet-apikeys`; služba převede jeho `Principal` na kontext:
|
|
917
|
+
|
|
918
|
+
```python
|
|
919
|
+
from fastapi import Depends
|
|
920
|
+
from sysnet_apikeys.fastapi import ApiKeyAuth
|
|
921
|
+
from sysnet_pyutils import RequestContext, current_request_context, set_request_context
|
|
922
|
+
|
|
923
|
+
auth = ApiKeyAuth(service) # sysnet-apikeys
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
async def authenticated(principal=Depends(auth)) -> RequestContext:
|
|
927
|
+
ctx = RequestContext(user_name=principal.client, agenda=principal.agenda)
|
|
928
|
+
set_request_context(ctx)
|
|
929
|
+
return ctx
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
# kdekoli později ve stejném požadavku:
|
|
933
|
+
agenda = current_request_context().agenda
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
- `RequestContext` je neměnný (`frozen`); `api_key` a `cert_pass` jsou typu `Secret` (repr a log skrývají hodnotu).
|
|
937
|
+
- `authenticated` je `True`, nese-li kontext identitu (`user_name` nebo `api_key`); samotná `agenda` ji nedělá.
|
|
938
|
+
- Kontext nastavujte v **`async def`** závislosti. `ContextVar` nastavený v synchronní závislosti (`def`, běží ve vlákně)
|
|
939
|
+
se do endpointu nepřenese; ověřeno pokusem s FastAPI 0.150. Případně použijte hodnotu vrácenou z `Depends`.
|
|
940
|
+
- Pro dočasné nastavení v bloku: `with use_request_context(ctx): ...` (vždy vrátí předchozí hodnotu).
|
|
941
|
+
|
|
904
942
|
#### Identifikátory a validace
|
|
905
943
|
|
|
906
944
|
```python
|
|
@@ -1409,6 +1447,15 @@ při běžném vydání již lokálně nic nenahrávejte na PyPI.
|
|
|
1409
1447
|
|
|
1410
1448
|
Podrobnosti nových změn jsou v [CHANGELOG.md](CHANGELOG.md).
|
|
1411
1449
|
|
|
1450
|
+
### Verze 1.9.0 (2026-10-28)
|
|
1451
|
+
|
|
1452
|
+
- 🧭 `RequestContext`: nosič identity jednoho požadavku v `ContextVar` (`set_request_context`, `current_request_context`, `use_request_context`); souběžné požadavky se nevidí. Ověřování API klíčů záměrně neobsahuje, patří knihovně `sysnet-apikeys`.
|
|
1453
|
+
- ⚠️ `Context.check_api_key()`, `Context.check_user()` a `api_keys_init()` jsou zastaralé (`DeprecationWarning` jednou za proces); chování pro platné konfigurace se nemění.
|
|
1454
|
+
- 🛡️ `Context`: vadná konfigurace nebo klíč jiného typu než neprázdný řetězec vrací `False` místo výjimky.
|
|
1455
|
+
- 🔐 `Context`: tajemství (`config`, `api_key`, `cert_pass`) nejsou ve `vars()`, v `repr` ani v `pickle`/`copy`; čtení atributů beze změny, nová metoda `safe_dict()`.
|
|
1456
|
+
|
|
1457
|
+
Podrobnosti a dopady na stávající aplikace jsou v [CHANGELOG.md](CHANGELOG.md).
|
|
1458
|
+
|
|
1412
1459
|
### Verze 1.8.0 (2026-10-27)
|
|
1413
1460
|
|
|
1414
1461
|
- 🔐 `Config`: ochrana secrets. Hodnotu v YAML lze zadat odkazem `${env:NAZEV}` nebo `${file:/absolutni/cesta}`; rozlišuje se při načtení na nový typ `Secret` (maskovaný `repr`, `str` a log), `store()` zapisuje zpět odkaz, nikdy hodnotu. Nově `Config.redacted()` a `Config.audit()`. Viz `docs/adr/0001-config-secrets.md`.
|
|
@@ -1555,6 +1602,6 @@ Tento projekt je licencován pod MIT licencí — viz [LICENSE](LICENSE) soubor.
|
|
|
1555
1602
|
|
|
1556
1603
|
**Vytvořeno s ❤️ týmem SYSNET**
|
|
1557
1604
|
|
|
1558
|
-
*Verze: 1.
|
|
1605
|
+
*Verze: 1.9.0*
|
|
1559
1606
|
|
|
1560
1607
|
*Datum: 2026-10-27*
|
|
@@ -6,7 +6,7 @@ Knihovna obsahuje základní, v aplikacích hojně používané, utility pro Pyt
|
|
|
6
6
|
|
|
7
7
|
[](https://www.python.org/downloads/)
|
|
8
8
|
[](LICENSE)
|
|
9
|
-
[](https://pypi.org/project/sysnet-pyutils/1.9.0/)
|
|
10
10
|
[](https://github.com/SYSNET-CZ/pyutils/actions/workflows/ci.yml)
|
|
11
11
|
[](tests/)
|
|
12
12
|
|
|
@@ -178,6 +178,44 @@ if ctx.check_api_key('some_key'):
|
|
|
178
178
|
print(f"Přihlášen jako: {ctx.user_name}")
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
> **`Context` je singleton se sdíleným stavem.** `check_api_key()` ukládá identitu do jediné instance, kterou sdílejí
|
|
182
|
+
> všechny souběžné požadavky. Ve webové službě (FastAPI) může jeden požadavek po `await` vidět identitu jiného.
|
|
183
|
+
> Pokus se dvěma souběžnými požadavky to potvrdil: `Context` vrátil první osobě jméno druhé.
|
|
184
|
+
> **Ověřování API klíčů patří knihovně `sysnet-apikeys`** (standard CML `api-keys.md`); `check_api_key()` a
|
|
185
|
+
> **Ověřování API klíčů patří knihovně `sysnet-apikeys`** (standard CML `api-keys.md`); `check_api_key()`,
|
|
186
|
+
> `check_user()` a `api_keys_init()` jsou zastaralé (`DeprecationWarning`) a zůstávají jen kvůli stávajícím službám.
|
|
187
|
+
> Tajemství (`config`, `api_key`, `cert_pass`) se čtou jako dřív, ale nejsou ve `vars()`, v `repr` ani v `pickle`/`copy`;
|
|
188
|
+
> pro diagnostiku slouží `ctx.safe_dict()`.
|
|
189
|
+
|
|
190
|
+
##### Per-request kontext (`RequestContext`)
|
|
191
|
+
|
|
192
|
+
`RequestContext` jen **nese** identitu ověřenou jinde, v `ContextVar`, takže ji každý požadavek vidí jen svou.
|
|
193
|
+
Ověřuje `sysnet-apikeys`; služba převede jeho `Principal` na kontext:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from fastapi import Depends
|
|
197
|
+
from sysnet_apikeys.fastapi import ApiKeyAuth
|
|
198
|
+
from sysnet_pyutils import RequestContext, current_request_context, set_request_context
|
|
199
|
+
|
|
200
|
+
auth = ApiKeyAuth(service) # sysnet-apikeys
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
async def authenticated(principal=Depends(auth)) -> RequestContext:
|
|
204
|
+
ctx = RequestContext(user_name=principal.client, agenda=principal.agenda)
|
|
205
|
+
set_request_context(ctx)
|
|
206
|
+
return ctx
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# kdekoli později ve stejném požadavku:
|
|
210
|
+
agenda = current_request_context().agenda
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
- `RequestContext` je neměnný (`frozen`); `api_key` a `cert_pass` jsou typu `Secret` (repr a log skrývají hodnotu).
|
|
214
|
+
- `authenticated` je `True`, nese-li kontext identitu (`user_name` nebo `api_key`); samotná `agenda` ji nedělá.
|
|
215
|
+
- Kontext nastavujte v **`async def`** závislosti. `ContextVar` nastavený v synchronní závislosti (`def`, běží ve vlákně)
|
|
216
|
+
se do endpointu nepřenese; ověřeno pokusem s FastAPI 0.150. Případně použijte hodnotu vrácenou z `Depends`.
|
|
217
|
+
- Pro dočasné nastavení v bloku: `with use_request_context(ctx): ...` (vždy vrátí předchozí hodnotu).
|
|
218
|
+
|
|
181
219
|
#### Identifikátory a validace
|
|
182
220
|
|
|
183
221
|
```python
|
|
@@ -686,6 +724,15 @@ při běžném vydání již lokálně nic nenahrávejte na PyPI.
|
|
|
686
724
|
|
|
687
725
|
Podrobnosti nových změn jsou v [CHANGELOG.md](CHANGELOG.md).
|
|
688
726
|
|
|
727
|
+
### Verze 1.9.0 (2026-10-28)
|
|
728
|
+
|
|
729
|
+
- 🧭 `RequestContext`: nosič identity jednoho požadavku v `ContextVar` (`set_request_context`, `current_request_context`, `use_request_context`); souběžné požadavky se nevidí. Ověřování API klíčů záměrně neobsahuje, patří knihovně `sysnet-apikeys`.
|
|
730
|
+
- ⚠️ `Context.check_api_key()`, `Context.check_user()` a `api_keys_init()` jsou zastaralé (`DeprecationWarning` jednou za proces); chování pro platné konfigurace se nemění.
|
|
731
|
+
- 🛡️ `Context`: vadná konfigurace nebo klíč jiného typu než neprázdný řetězec vrací `False` místo výjimky.
|
|
732
|
+
- 🔐 `Context`: tajemství (`config`, `api_key`, `cert_pass`) nejsou ve `vars()`, v `repr` ani v `pickle`/`copy`; čtení atributů beze změny, nová metoda `safe_dict()`.
|
|
733
|
+
|
|
734
|
+
Podrobnosti a dopady na stávající aplikace jsou v [CHANGELOG.md](CHANGELOG.md).
|
|
735
|
+
|
|
689
736
|
### Verze 1.8.0 (2026-10-27)
|
|
690
737
|
|
|
691
738
|
- 🔐 `Config`: ochrana secrets. Hodnotu v YAML lze zadat odkazem `${env:NAZEV}` nebo `${file:/absolutni/cesta}`; rozlišuje se při načtení na nový typ `Secret` (maskovaný `repr`, `str` a log), `store()` zapisuje zpět odkaz, nikdy hodnotu. Nově `Config.redacted()` a `Config.audit()`. Viz `docs/adr/0001-config-secrets.md`.
|
|
@@ -832,6 +879,6 @@ Tento projekt je licencován pod MIT licencí — viz [LICENSE](LICENSE) soubor.
|
|
|
832
879
|
|
|
833
880
|
**Vytvořeno s ❤️ týmem SYSNET**
|
|
834
881
|
|
|
835
|
-
*Verze: 1.
|
|
882
|
+
*Verze: 1.9.0*
|
|
836
883
|
|
|
837
884
|
*Datum: 2026-10-27*
|
|
@@ -116,6 +116,13 @@ from sysnet_pyutils.models.general import (
|
|
|
116
116
|
WorkflowType,
|
|
117
117
|
calculate_status,
|
|
118
118
|
)
|
|
119
|
+
from sysnet_pyutils.request_context import (
|
|
120
|
+
RequestContext,
|
|
121
|
+
current_request_context,
|
|
122
|
+
reset_request_context,
|
|
123
|
+
set_request_context,
|
|
124
|
+
use_request_context,
|
|
125
|
+
)
|
|
119
126
|
|
|
120
127
|
# Syslog
|
|
121
128
|
from sysnet_pyutils.syslog import SyslogFactory
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Nosič kontextu jednoho požadavku (ASGI-safe doplněk ke třídě ``Context``).
|
|
2
|
+
|
|
3
|
+
``sysnet_pyutils.Context`` je singleton: drží stav posledního ověření a sdílí ho všechny souběžné požadavky.
|
|
4
|
+
Pokud si dva požadavky vymění stav, jeden z nich uvidí identitu druhého. Tento modul nabízí jen **nosič**
|
|
5
|
+
identity bez sdíleného stavu:
|
|
6
|
+
|
|
7
|
+
* neměnný :class:`RequestContext`,
|
|
8
|
+
* ``set_request_context`` / ``current_request_context`` / ``reset_request_context`` / ``use_request_context``
|
|
9
|
+
ukládají kontext do :class:`contextvars.ContextVar`, takže každá asynchronní úloha (požadavek) vidí jen svůj.
|
|
10
|
+
|
|
11
|
+
**Modul neověřuje API klíče.** Ověřování, hashování a správa klíčů patří výhradně knihovně ``sysnet-apikeys``
|
|
12
|
+
(standard CML ``constitution/standards/api-keys.md``, pravidlo 1). Identitu, kterou ``sysnet-apikeys`` vrátí
|
|
13
|
+
(``Principal`` s ``client``, ``agenda`` a ``scopes``), si služba převede na :class:`RequestContext` sama::
|
|
14
|
+
|
|
15
|
+
async def authenticated(principal: Principal = Depends(auth)) -> RequestContext:
|
|
16
|
+
ctx = RequestContext(user_name=principal.client, agenda=principal.agenda)
|
|
17
|
+
set_request_context(ctx)
|
|
18
|
+
return ctx
|
|
19
|
+
|
|
20
|
+
``ContextVar`` nastavený v **synchronní** závislosti (``def``, běží ve vlákně) se do endpointu nepřenese. Nastavujte
|
|
21
|
+
ho v ``async def`` závislosti, nebo použijte vrácenou hodnotu (``Depends``) přímo.
|
|
22
|
+
|
|
23
|
+
Starý singleton ``Context`` zůstává beze změny kvůli stávajícím konzumentům.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import contextlib
|
|
29
|
+
from collections.abc import Iterator
|
|
30
|
+
from contextvars import ContextVar, Token
|
|
31
|
+
from dataclasses import dataclass
|
|
32
|
+
|
|
33
|
+
from sysnet_pyutils.utils import Secret
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(frozen=True)
|
|
37
|
+
class RequestContext:
|
|
38
|
+
"""Neměnný kontext jednoho požadavku.
|
|
39
|
+
|
|
40
|
+
``frozen`` brání tomu, aby se změnou sdílené prázdné instance změnil stav všech úloh, které kontext ještě
|
|
41
|
+
nenastavily. ``api_key`` a ``cert_pass`` jsou typu :class:`Secret`, takže se neobjeví v ``repr``, ``str`` ani
|
|
42
|
+
v logu; čistou hodnotu vrací ``.reveal()``.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
api_key: Secret | None = None
|
|
46
|
+
agenda: str | None = None
|
|
47
|
+
user_name: str | None = None
|
|
48
|
+
cert_file: str | None = None
|
|
49
|
+
cert_pass: Secret | None = None
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def authenticated(self) -> bool:
|
|
53
|
+
"""``True``, pokud kontext nese identitu volajícího (``user_name`` nebo ``api_key``).
|
|
54
|
+
|
|
55
|
+
``sysnet-apikeys`` otevřený klíč nevrací (ukládá se jen hash), proto ``api_key`` bývá prázdný a identitu
|
|
56
|
+
nese ``user_name`` (klient z ``Principal``). Samotná ``agenda`` identitou není.
|
|
57
|
+
"""
|
|
58
|
+
return self.api_key is not None or self.user_name is not None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_EMPTY: RequestContext = RequestContext()
|
|
62
|
+
_request_ctx: ContextVar[RequestContext] = ContextVar("sysnet_pyutils_request_context", default=_EMPTY)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def current_request_context() -> RequestContext:
|
|
66
|
+
"""Kontext aktuální úlohy; pokud nebyl nastaven, vrací prázdný (neověřený)."""
|
|
67
|
+
return _request_ctx.get()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def set_request_context(ctx: RequestContext) -> Token[RequestContext]:
|
|
71
|
+
"""Nastaví kontext pro aktuální úlohu. Vrací token pro :func:`reset_request_context`."""
|
|
72
|
+
return _request_ctx.set(ctx)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def reset_request_context(token: Token[RequestContext] | None = None) -> None:
|
|
76
|
+
"""Vrátí předchozí hodnotu (podle ``token``), nebo bez tokenu nastaví prázdný kontext."""
|
|
77
|
+
if token is not None:
|
|
78
|
+
_request_ctx.reset(token)
|
|
79
|
+
else:
|
|
80
|
+
_request_ctx.set(_EMPTY)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@contextlib.contextmanager
|
|
84
|
+
def use_request_context(ctx: RequestContext) -> Iterator[RequestContext]:
|
|
85
|
+
"""Nastaví kontext po dobu bloku a vždy ho vrátí zpět (i při výjimce)."""
|
|
86
|
+
token = set_request_context(ctx)
|
|
87
|
+
try:
|
|
88
|
+
yield ctx
|
|
89
|
+
finally:
|
|
90
|
+
reset_request_context(token)
|
|
@@ -10,6 +10,7 @@ import secrets
|
|
|
10
10
|
import stat
|
|
11
11
|
import sys
|
|
12
12
|
import traceback
|
|
13
|
+
import warnings
|
|
13
14
|
from datetime import date, datetime, timedelta
|
|
14
15
|
from typing import Any
|
|
15
16
|
from urllib.parse import quote
|
|
@@ -43,6 +44,25 @@ class ConfigParseError(ConfigError, yaml.YAMLError):
|
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
46
|
|
|
47
|
+
_DEPRECATION_WARNED: set[str] = set()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _warn_deprecated_once(name: str, stacklevel: int = 3) -> None:
|
|
51
|
+
"""Jednou za proces upozorní, že ověřování příchozích API klíčů patří knihovně ``sysnet-apikeys``.
|
|
52
|
+
|
|
53
|
+
Zpráva neobsahuje žádné hodnoty z konfigurace. ``stacklevel`` míří na volajícího zastaralé funkce.
|
|
54
|
+
"""
|
|
55
|
+
if name in _DEPRECATION_WARNED:
|
|
56
|
+
return
|
|
57
|
+
_DEPRECATION_WARNED.add(name)
|
|
58
|
+
warnings.warn(
|
|
59
|
+
f"{name} je zastaralé: správu a ověřování API klíčů zajišťuje knihovna sysnet-apikeys "
|
|
60
|
+
"(standard CML constitution/standards/api-keys.md). Chování se nemění, odstranění je plánováno až v major vydání.",
|
|
61
|
+
DeprecationWarning,
|
|
62
|
+
stacklevel=stacklevel,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
46
66
|
class Singleton(type):
|
|
47
67
|
"""
|
|
48
68
|
Třída Singleton
|
|
@@ -731,10 +751,15 @@ def api_keys_init(agenda: str = "main", amount: int = 4) -> list[dict]:
|
|
|
731
751
|
"""
|
|
732
752
|
Vygeneruje klíče pro API
|
|
733
753
|
|
|
754
|
+
.. deprecated:: 1.9.0
|
|
755
|
+
Příchozí API klíče spravuje ``sysnet-apikeys`` (standard CML ``api-keys.md``); klíče v otevřené podobě
|
|
756
|
+
v YAML se nemají ukládat. Funkce zůstává kvůli stávajícím službám.
|
|
757
|
+
|
|
734
758
|
:param agenda: Název agendy, pro kterou se klíče generují
|
|
735
759
|
:param amount: Počet vygenerovaných klíčů
|
|
736
760
|
:return: seznam vygenerovaných klíčů
|
|
737
761
|
"""
|
|
762
|
+
_warn_deprecated_once("api_keys_init")
|
|
738
763
|
out = []
|
|
739
764
|
for i in range(amount):
|
|
740
765
|
out.append(api_key_next(f"{agenda} {i + 1}"))
|
|
@@ -1505,9 +1530,41 @@ def parse_ldap_name(ldap_name: str) -> tuple[str | None, str | None]:
|
|
|
1505
1530
|
return cn, ldap_name
|
|
1506
1531
|
|
|
1507
1532
|
|
|
1533
|
+
class _SecretVault:
|
|
1534
|
+
"""Schránka na tajemství třídy ``Context`` (``config``, ``api_key``, ``cert_pass``).
|
|
1535
|
+
|
|
1536
|
+
Tajemství nejsou ve ``vars()`` ani v ``__dict__`` objektu; ve slovníku je jen tato schránka, která se
|
|
1537
|
+
vypisuje maskovaně a při ``pickle`` i ``copy`` se přenese prázdná.
|
|
1538
|
+
"""
|
|
1539
|
+
|
|
1540
|
+
__slots__ = ("api_key", "cert_pass", "config")
|
|
1541
|
+
|
|
1542
|
+
def __init__(self) -> None:
|
|
1543
|
+
self.api_key = None
|
|
1544
|
+
self.cert_pass = None
|
|
1545
|
+
self.config = None
|
|
1546
|
+
|
|
1547
|
+
def __repr__(self) -> str:
|
|
1548
|
+
return "<tajemství skryta>"
|
|
1549
|
+
|
|
1550
|
+
def __reduce__(self):
|
|
1551
|
+
return (_SecretVault, ())
|
|
1552
|
+
|
|
1553
|
+
|
|
1508
1554
|
class Context(LoggedObject, metaclass=Singleton):
|
|
1555
|
+
"""Singleton se stavem posledního ověření (zastaralé pro ověřování API klíčů).
|
|
1556
|
+
|
|
1557
|
+
Instance je sdílená všemi souběžnými požadavky, takže po ``await`` může jeden požadavek vidět identitu jiného.
|
|
1558
|
+
Ověřování API klíčů patří knihovně ``sysnet-apikeys``; identitu požadavku nese ``RequestContext``
|
|
1559
|
+
(``contextvars``, bez sdíleného stavu). Třída zůstává beze změny chování kvůli stávajícím službám.
|
|
1560
|
+
|
|
1561
|
+
Tajemství (``config``, ``api_key``, ``cert_pass``) se čtou jako dřív, ale nejsou ve ``vars()``, v ``repr`` ani
|
|
1562
|
+
v ``pickle``/``copy``; pro diagnostiku slouží :meth:`safe_dict`.
|
|
1563
|
+
"""
|
|
1564
|
+
|
|
1509
1565
|
def __init__(self, config=None, api_key=None, user_name=None, agenda=None, cert_file=None, cert_pass=None):
|
|
1510
1566
|
super().__init__(object_name="CONTEXT")
|
|
1567
|
+
self._vault = _SecretVault()
|
|
1511
1568
|
self.config = config
|
|
1512
1569
|
if self.config is None:
|
|
1513
1570
|
self.log.error(f"{self.name} FAILED - Missing config")
|
|
@@ -1519,6 +1576,62 @@ class Context(LoggedObject, metaclass=Singleton):
|
|
|
1519
1576
|
self.cert_pass = cert_pass
|
|
1520
1577
|
self.log.info(f"{self.name} Created")
|
|
1521
1578
|
|
|
1579
|
+
@property
|
|
1580
|
+
def config(self):
|
|
1581
|
+
"""Konfigurace (obsahuje tajemství, proto není ve ``vars()``)."""
|
|
1582
|
+
return self._vault.config
|
|
1583
|
+
|
|
1584
|
+
@config.setter
|
|
1585
|
+
def config(self, value):
|
|
1586
|
+
self._vault.config = value
|
|
1587
|
+
|
|
1588
|
+
@property
|
|
1589
|
+
def api_key(self):
|
|
1590
|
+
"""Ověřený API klíč (tajemství, ve ``vars()`` není)."""
|
|
1591
|
+
return self._vault.api_key
|
|
1592
|
+
|
|
1593
|
+
@api_key.setter
|
|
1594
|
+
def api_key(self, value):
|
|
1595
|
+
self._vault.api_key = value
|
|
1596
|
+
|
|
1597
|
+
@property
|
|
1598
|
+
def cert_pass(self):
|
|
1599
|
+
"""Heslo certifikátu (tajemství, ve ``vars()`` není). Čte se jako obyčejný řetězec."""
|
|
1600
|
+
return self._vault.cert_pass
|
|
1601
|
+
|
|
1602
|
+
@cert_pass.setter
|
|
1603
|
+
def cert_pass(self, value):
|
|
1604
|
+
self._vault.cert_pass = value
|
|
1605
|
+
|
|
1606
|
+
def safe_dict(self) -> dict:
|
|
1607
|
+
"""Bezpečný diagnostický výpis: identita a certifikát, tajemství jako ``***`` (nebo ``None``, když chybí)."""
|
|
1608
|
+
|
|
1609
|
+
def mask(value):
|
|
1610
|
+
return None if value is None else "***"
|
|
1611
|
+
|
|
1612
|
+
return {
|
|
1613
|
+
"agenda": self.agenda,
|
|
1614
|
+
"user_name": self.user_name,
|
|
1615
|
+
"authenticated": self.authenticated,
|
|
1616
|
+
"cert_file": self.cert_file,
|
|
1617
|
+
"api_key": mask(self.api_key),
|
|
1618
|
+
"cert_pass": mask(self.cert_pass),
|
|
1619
|
+
"config": mask(self.config),
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
def __repr__(self) -> str:
|
|
1623
|
+
info = self.safe_dict()
|
|
1624
|
+
parts = ", ".join(
|
|
1625
|
+
f"{k}={v!r}" if k not in ("api_key", "cert_pass", "config") else f"{k}={v}" for k, v in info.items()
|
|
1626
|
+
)
|
|
1627
|
+
return f"{type(self).__name__}({parts})"
|
|
1628
|
+
|
|
1629
|
+
def __getstate__(self):
|
|
1630
|
+
"""Pro ``pickle`` a ``copy``: tajemství se nepřenášejí (kopie má prázdnou schránku)."""
|
|
1631
|
+
state = self.__dict__.copy()
|
|
1632
|
+
state["_vault"] = _SecretVault()
|
|
1633
|
+
return state
|
|
1634
|
+
|
|
1522
1635
|
def _singleton_reconfigure(
|
|
1523
1636
|
self, config=None, api_key=None, user_name=None, agenda=None, cert_file=None, cert_pass=None
|
|
1524
1637
|
):
|
|
@@ -1539,49 +1652,80 @@ class Context(LoggedObject, metaclass=Singleton):
|
|
|
1539
1652
|
self.cert_pass = None
|
|
1540
1653
|
self.log.info(f"{self.name} Cleared")
|
|
1541
1654
|
|
|
1655
|
+
def _agendas(self):
|
|
1656
|
+
"""Dvojice (agenda, sekce) jen pro sekce typu slovník; vadná konfigurace nevyhodí výjimku."""
|
|
1657
|
+
if not isinstance(self.config, dict):
|
|
1658
|
+
self.log.error(f"{self.name} - config is not a dict, authentication is not possible")
|
|
1659
|
+
return []
|
|
1660
|
+
return [(agenda, section) for agenda, section in self.config.items() if isinstance(section, dict)]
|
|
1661
|
+
|
|
1662
|
+
def _load_cert(self, section):
|
|
1663
|
+
"""Načte cestu a heslo certifikátu agendy; neplatná sekce ``cert`` se přeskočí."""
|
|
1664
|
+
cert = section.get(CONFIG_CERT)
|
|
1665
|
+
if not isinstance(cert, dict):
|
|
1666
|
+
return
|
|
1667
|
+
if CONFIG_PATH in cert:
|
|
1668
|
+
self.cert_file = cert[CONFIG_PATH]
|
|
1669
|
+
if CONFIG_PASSWORD in cert:
|
|
1670
|
+
self.cert_pass = cert[CONFIG_PASSWORD]
|
|
1671
|
+
|
|
1542
1672
|
def check_api_key(self, api_key):
|
|
1673
|
+
"""Ověří API klíč podle ``config[agenda]['api_keys']``.
|
|
1674
|
+
|
|
1675
|
+
.. deprecated:: 1.9.0
|
|
1676
|
+
Ověřování API klíčů patří knihovně ``sysnet-apikeys`` (standard CML ``api-keys.md``). Chování se nemění.
|
|
1677
|
+
|
|
1678
|
+
Vstup musí být neprázdný řetězec, jinak vrací ``False``. Vadná konfigurace (sekce ``api_keys`` jiného typu
|
|
1679
|
+
než seznam, položky jiného typu než slovník) se přeskočí s varováním bez hodnot a nevyhodí výjimku.
|
|
1680
|
+
"""
|
|
1681
|
+
_warn_deprecated_once("Context.check_api_key")
|
|
1543
1682
|
self.clear()
|
|
1544
|
-
if api_key
|
|
1683
|
+
if not isinstance(api_key, str) or api_key == "":
|
|
1545
1684
|
return False
|
|
1546
|
-
for agenda in self.
|
|
1547
|
-
if not
|
|
1685
|
+
for agenda, section in self._agendas():
|
|
1686
|
+
if CONFIG_API_KEYS not in section:
|
|
1548
1687
|
continue
|
|
1549
|
-
|
|
1688
|
+
entries = section[CONFIG_API_KEYS]
|
|
1689
|
+
if not isinstance(entries, list):
|
|
1690
|
+
self.log.warning(f"{self.name} - '{CONFIG_API_KEYS}' of agenda '{agenda}' is not a list, skipped")
|
|
1550
1691
|
continue
|
|
1551
|
-
for ak in
|
|
1552
|
-
if
|
|
1692
|
+
for ak in entries:
|
|
1693
|
+
if not isinstance(ak, dict):
|
|
1694
|
+
continue
|
|
1695
|
+
for candidate, user in ak.items():
|
|
1696
|
+
if not isinstance(candidate, str):
|
|
1697
|
+
self.log.warning(
|
|
1698
|
+
f"{self.name} - agenda '{agenda}' has a non-string API key ({type(candidate).__name__}), "
|
|
1699
|
+
"compared as text"
|
|
1700
|
+
)
|
|
1701
|
+
if str(candidate) != api_key:
|
|
1702
|
+
continue
|
|
1553
1703
|
self.api_key = api_key
|
|
1554
1704
|
self.agenda = agenda
|
|
1555
|
-
self.user_name =
|
|
1705
|
+
self.user_name = user
|
|
1556
1706
|
self.authenticated = True
|
|
1557
|
-
|
|
1558
|
-
if CONFIG_PATH in self.config[agenda][CONFIG_CERT]:
|
|
1559
|
-
self.cert_file = self.config[agenda][CONFIG_CERT][CONFIG_PATH]
|
|
1560
|
-
if CONFIG_PASSWORD in self.config[agenda][CONFIG_CERT]:
|
|
1561
|
-
self.cert_pass = self.config[agenda][CONFIG_CERT][CONFIG_PASSWORD]
|
|
1707
|
+
self._load_cert(section)
|
|
1562
1708
|
self.log.info(f"{self.name} - User '{self.user_name}' logged in")
|
|
1563
|
-
|
|
1564
|
-
if self.authenticated:
|
|
1565
|
-
break
|
|
1709
|
+
return self.authenticated
|
|
1566
1710
|
return self.authenticated
|
|
1567
1711
|
|
|
1568
1712
|
def check_user(self, username, password):
|
|
1713
|
+
"""Ověří jméno a heslo podle ``config[agenda]['paswd']``.
|
|
1714
|
+
|
|
1715
|
+
.. deprecated:: 1.9.0
|
|
1716
|
+
Viz :meth:`check_api_key`. Chování se nemění; vadná sekce ``paswd`` se přeskočí bez výjimky.
|
|
1717
|
+
"""
|
|
1718
|
+
_warn_deprecated_once("Context.check_user")
|
|
1569
1719
|
self.clear()
|
|
1570
|
-
for agenda in self.
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
if CONFIG_PASSWD not in self.config[agenda]:
|
|
1720
|
+
for agenda, section in self._agendas():
|
|
1721
|
+
users = section.get(CONFIG_PASSWD)
|
|
1722
|
+
if not isinstance(users, dict):
|
|
1574
1723
|
continue
|
|
1575
|
-
if username in
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
if CONFIG_PATH in self.config[agenda][CONFIG_CERT]:
|
|
1583
|
-
self.cert_file = self.config[agenda][CONFIG_CERT][CONFIG_PATH]
|
|
1584
|
-
if CONFIG_PASSWORD in self.config[agenda][CONFIG_CERT]:
|
|
1585
|
-
self.cert_pass = self.config[agenda][CONFIG_CERT][CONFIG_PASSWORD]
|
|
1586
|
-
break
|
|
1724
|
+
if username in users and users[username] == password:
|
|
1725
|
+
self.api_key = None
|
|
1726
|
+
self.agenda = agenda
|
|
1727
|
+
self.user_name = username
|
|
1728
|
+
self.authenticated = True
|
|
1729
|
+
self._load_cert(section)
|
|
1730
|
+
break
|
|
1587
1731
|
return self.authenticated
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysnet-pyutils
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: SYSNET Python Utilities - kompletní sada nástrojů pro Python aplikace
|
|
5
5
|
Author-email: Data Developer <info@sysnet.cz>
|
|
6
6
|
Maintainer-email: SYSNET Development Team <info@sysnet.cz>
|
|
@@ -729,7 +729,7 @@ Knihovna obsahuje základní, v aplikacích hojně používané, utility pro Pyt
|
|
|
729
729
|
|
|
730
730
|
[](https://www.python.org/downloads/)
|
|
731
731
|
[](LICENSE)
|
|
732
|
-
[](https://pypi.org/project/sysnet-pyutils/1.9.0/)
|
|
733
733
|
[](https://github.com/SYSNET-CZ/pyutils/actions/workflows/ci.yml)
|
|
734
734
|
[](tests/)
|
|
735
735
|
|
|
@@ -901,6 +901,44 @@ if ctx.check_api_key('some_key'):
|
|
|
901
901
|
print(f"Přihlášen jako: {ctx.user_name}")
|
|
902
902
|
```
|
|
903
903
|
|
|
904
|
+
> **`Context` je singleton se sdíleným stavem.** `check_api_key()` ukládá identitu do jediné instance, kterou sdílejí
|
|
905
|
+
> všechny souběžné požadavky. Ve webové službě (FastAPI) může jeden požadavek po `await` vidět identitu jiného.
|
|
906
|
+
> Pokus se dvěma souběžnými požadavky to potvrdil: `Context` vrátil první osobě jméno druhé.
|
|
907
|
+
> **Ověřování API klíčů patří knihovně `sysnet-apikeys`** (standard CML `api-keys.md`); `check_api_key()` a
|
|
908
|
+
> **Ověřování API klíčů patří knihovně `sysnet-apikeys`** (standard CML `api-keys.md`); `check_api_key()`,
|
|
909
|
+
> `check_user()` a `api_keys_init()` jsou zastaralé (`DeprecationWarning`) a zůstávají jen kvůli stávajícím službám.
|
|
910
|
+
> Tajemství (`config`, `api_key`, `cert_pass`) se čtou jako dřív, ale nejsou ve `vars()`, v `repr` ani v `pickle`/`copy`;
|
|
911
|
+
> pro diagnostiku slouží `ctx.safe_dict()`.
|
|
912
|
+
|
|
913
|
+
##### Per-request kontext (`RequestContext`)
|
|
914
|
+
|
|
915
|
+
`RequestContext` jen **nese** identitu ověřenou jinde, v `ContextVar`, takže ji každý požadavek vidí jen svou.
|
|
916
|
+
Ověřuje `sysnet-apikeys`; služba převede jeho `Principal` na kontext:
|
|
917
|
+
|
|
918
|
+
```python
|
|
919
|
+
from fastapi import Depends
|
|
920
|
+
from sysnet_apikeys.fastapi import ApiKeyAuth
|
|
921
|
+
from sysnet_pyutils import RequestContext, current_request_context, set_request_context
|
|
922
|
+
|
|
923
|
+
auth = ApiKeyAuth(service) # sysnet-apikeys
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
async def authenticated(principal=Depends(auth)) -> RequestContext:
|
|
927
|
+
ctx = RequestContext(user_name=principal.client, agenda=principal.agenda)
|
|
928
|
+
set_request_context(ctx)
|
|
929
|
+
return ctx
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
# kdekoli později ve stejném požadavku:
|
|
933
|
+
agenda = current_request_context().agenda
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
- `RequestContext` je neměnný (`frozen`); `api_key` a `cert_pass` jsou typu `Secret` (repr a log skrývají hodnotu).
|
|
937
|
+
- `authenticated` je `True`, nese-li kontext identitu (`user_name` nebo `api_key`); samotná `agenda` ji nedělá.
|
|
938
|
+
- Kontext nastavujte v **`async def`** závislosti. `ContextVar` nastavený v synchronní závislosti (`def`, běží ve vlákně)
|
|
939
|
+
se do endpointu nepřenese; ověřeno pokusem s FastAPI 0.150. Případně použijte hodnotu vrácenou z `Depends`.
|
|
940
|
+
- Pro dočasné nastavení v bloku: `with use_request_context(ctx): ...` (vždy vrátí předchozí hodnotu).
|
|
941
|
+
|
|
904
942
|
#### Identifikátory a validace
|
|
905
943
|
|
|
906
944
|
```python
|
|
@@ -1409,6 +1447,15 @@ při běžném vydání již lokálně nic nenahrávejte na PyPI.
|
|
|
1409
1447
|
|
|
1410
1448
|
Podrobnosti nových změn jsou v [CHANGELOG.md](CHANGELOG.md).
|
|
1411
1449
|
|
|
1450
|
+
### Verze 1.9.0 (2026-10-28)
|
|
1451
|
+
|
|
1452
|
+
- 🧭 `RequestContext`: nosič identity jednoho požadavku v `ContextVar` (`set_request_context`, `current_request_context`, `use_request_context`); souběžné požadavky se nevidí. Ověřování API klíčů záměrně neobsahuje, patří knihovně `sysnet-apikeys`.
|
|
1453
|
+
- ⚠️ `Context.check_api_key()`, `Context.check_user()` a `api_keys_init()` jsou zastaralé (`DeprecationWarning` jednou za proces); chování pro platné konfigurace se nemění.
|
|
1454
|
+
- 🛡️ `Context`: vadná konfigurace nebo klíč jiného typu než neprázdný řetězec vrací `False` místo výjimky.
|
|
1455
|
+
- 🔐 `Context`: tajemství (`config`, `api_key`, `cert_pass`) nejsou ve `vars()`, v `repr` ani v `pickle`/`copy`; čtení atributů beze změny, nová metoda `safe_dict()`.
|
|
1456
|
+
|
|
1457
|
+
Podrobnosti a dopady na stávající aplikace jsou v [CHANGELOG.md](CHANGELOG.md).
|
|
1458
|
+
|
|
1412
1459
|
### Verze 1.8.0 (2026-10-27)
|
|
1413
1460
|
|
|
1414
1461
|
- 🔐 `Config`: ochrana secrets. Hodnotu v YAML lze zadat odkazem `${env:NAZEV}` nebo `${file:/absolutni/cesta}`; rozlišuje se při načtení na nový typ `Secret` (maskovaný `repr`, `str` a log), `store()` zapisuje zpět odkaz, nikdy hodnotu. Nově `Config.redacted()` a `Config.audit()`. Viz `docs/adr/0001-config-secrets.md`.
|
|
@@ -1555,6 +1602,6 @@ Tento projekt je licencován pod MIT licencí — viz [LICENSE](LICENSE) soubor.
|
|
|
1555
1602
|
|
|
1556
1603
|
**Vytvořeno s ❤️ týmem SYSNET**
|
|
1557
1604
|
|
|
1558
|
-
*Verze: 1.
|
|
1605
|
+
*Verze: 1.9.0*
|
|
1559
1606
|
|
|
1560
1607
|
*Datum: 2026-10-27*
|
|
@@ -9,6 +9,7 @@ sysnet_pyutils/domino.py
|
|
|
9
9
|
sysnet_pyutils/geo.py
|
|
10
10
|
sysnet_pyutils/ident.py
|
|
11
11
|
sysnet_pyutils/log.py
|
|
12
|
+
sysnet_pyutils/request_context.py
|
|
12
13
|
sysnet_pyutils/ses.py
|
|
13
14
|
sysnet_pyutils/tools.py
|
|
14
15
|
sysnet_pyutils/utils.py
|
|
@@ -30,6 +31,8 @@ sysnet_pyutils/syslog/__init__.py
|
|
|
30
31
|
sysnet_pyutils/syslog/factory.py
|
|
31
32
|
tests/test_config_secrets.py
|
|
32
33
|
tests/test_config_store.py
|
|
34
|
+
tests/test_context_legacy_robustness.py
|
|
35
|
+
tests/test_context_secrets_masking.py
|
|
33
36
|
tests/test_cron_utils.py
|
|
34
37
|
tests/test_cron_validation_edge.py
|
|
35
38
|
tests/test_data_utils.py
|
|
@@ -49,6 +52,7 @@ tests/test_models_general.py
|
|
|
49
52
|
tests/test_models_general_edge.py
|
|
50
53
|
tests/test_package_init_version.py
|
|
51
54
|
tests/test_recursive_update.py
|
|
55
|
+
tests/test_request_context.py
|
|
52
56
|
tests/test_ses.py
|
|
53
57
|
tests/test_ses_importerror_branch.py
|
|
54
58
|
tests/test_syslog_factory.py
|