python3-commons 0.8.32__tar.gz → 0.8.34__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.
Files changed (57) hide show
  1. {python3_commons-0.8.32 → python3_commons-0.8.34}/.pre-commit-config.yaml +3 -3
  2. {python3_commons-0.8.32/src/python3_commons.egg-info → python3_commons-0.8.34}/PKG-INFO +6 -6
  3. {python3_commons-0.8.32 → python3_commons-0.8.34}/pyproject.toml +9 -8
  4. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/serializers/msgspec.py +27 -9
  5. {python3_commons-0.8.32 → python3_commons-0.8.34/src/python3_commons.egg-info}/PKG-INFO +6 -6
  6. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons.egg-info/SOURCES.txt +1 -0
  7. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons.egg-info/requires.txt +5 -5
  8. python3_commons-0.8.34/tests/conftest.py +117 -0
  9. python3_commons-0.8.34/tests/test_cache.py +8 -0
  10. {python3_commons-0.8.32 → python3_commons-0.8.34}/tests/test_msgpack.py +1 -1
  11. {python3_commons-0.8.32 → python3_commons-0.8.34}/tests/test_msgspec.py +24 -9
  12. {python3_commons-0.8.32 → python3_commons-0.8.34}/uv.lock +365 -345
  13. python3_commons-0.8.32/tests/conftest.py +0 -79
  14. {python3_commons-0.8.32 → python3_commons-0.8.34}/.coveragerc +0 -0
  15. {python3_commons-0.8.32 → python3_commons-0.8.34}/.github/workflows/python-publish.yaml +0 -0
  16. {python3_commons-0.8.32 → python3_commons-0.8.34}/.github/workflows/release-on-tag-push.yml +0 -0
  17. {python3_commons-0.8.32 → python3_commons-0.8.34}/.gitignore +0 -0
  18. {python3_commons-0.8.32 → python3_commons-0.8.34}/.python-version +0 -0
  19. {python3_commons-0.8.32 → python3_commons-0.8.34}/AUTHORS.rst +0 -0
  20. {python3_commons-0.8.32 → python3_commons-0.8.34}/CHANGELOG.rst +0 -0
  21. {python3_commons-0.8.32 → python3_commons-0.8.34}/LICENSE +0 -0
  22. {python3_commons-0.8.32 → python3_commons-0.8.34}/README.md +0 -0
  23. {python3_commons-0.8.32 → python3_commons-0.8.34}/README.rst +0 -0
  24. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/Makefile +0 -0
  25. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/_static/.gitignore +0 -0
  26. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/authors.rst +0 -0
  27. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/changelog.rst +0 -0
  28. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/conf.py +0 -0
  29. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/index.rst +0 -0
  30. {python3_commons-0.8.32 → python3_commons-0.8.34}/docs/license.rst +0 -0
  31. {python3_commons-0.8.32 → python3_commons-0.8.34}/setup.cfg +0 -0
  32. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/__init__.py +0 -0
  33. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/api_client.py +0 -0
  34. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/audit.py +0 -0
  35. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/auth.py +0 -0
  36. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/cache.py +0 -0
  37. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/conf.py +0 -0
  38. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/db/__init__.py +0 -0
  39. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/db/helpers.py +0 -0
  40. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/db/models/__init__.py +0 -0
  41. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/db/models/auth.py +0 -0
  42. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/db/models/common.py +0 -0
  43. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/db/models/rbac.py +0 -0
  44. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/fs.py +0 -0
  45. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/helpers.py +0 -0
  46. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/log/__init__.py +0 -0
  47. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/log/filters.py +0 -0
  48. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/log/formatters.py +0 -0
  49. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/object_storage.py +0 -0
  50. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/permissions.py +0 -0
  51. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/serializers/__init__.py +0 -0
  52. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/serializers/json.py +0 -0
  53. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons/serializers/msgpack.py +0 -0
  54. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons.egg-info/dependency_links.txt +0 -0
  55. {python3_commons-0.8.32 → python3_commons-0.8.34}/src/python3_commons.egg-info/top_level.txt +0 -0
  56. {python3_commons-0.8.32 → python3_commons-0.8.34}/tests/test_audit.py +0 -0
  57. {python3_commons-0.8.32 → python3_commons-0.8.34}/tests/test_helpers.py +0 -0
@@ -1,12 +1,12 @@
1
1
  repos:
2
2
  - repo: https://github.com/astral-sh/uv-pre-commit
3
- rev: 0.7.3
3
+ rev: 0.7.13
4
4
  hooks:
5
5
  - id: uv-lock
6
- - id: uv-export
6
+ # - id: uv-export
7
7
 
8
8
  - repo: https://github.com/astral-sh/ruff-pre-commit
9
- rev: v0.11.10
9
+ rev: v0.12.0
10
10
  hooks:
11
11
  # Run the linter.
12
12
  - id: ruff-check
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.8.32
3
+ Version: 0.8.34
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -12,17 +12,17 @@ Requires-Python: ==3.13.*
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiohttp[speedups]~=3.12.2
15
+ Requires-Dist: aiohttp[speedups]~=3.12.13
16
16
  Requires-Dist: asyncpg~=0.30.0
17
17
  Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
18
18
  Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
19
19
  Requires-Dist: lxml~=5.4.0
20
20
  Requires-Dist: minio~=7.2.15
21
- Requires-Dist: msgpack~=1.1.0
21
+ Requires-Dist: msgpack~=1.1.1
22
22
  Requires-Dist: msgspec~=0.19.0
23
- Requires-Dist: pydantic[email]~=2.11.5
24
- Requires-Dist: pydantic-settings~=2.9.1
25
- Requires-Dist: python-jose==3.4.0
23
+ Requires-Dist: pydantic[email]~=2.11.7
24
+ Requires-Dist: pydantic-settings~=2.10.0
25
+ Requires-Dist: python-jose==3.5.0
26
26
  Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
27
27
  Requires-Dist: valkey[libvalkey]~=6.1.0
28
28
  Requires-Dist: zeep~=4.3.1
@@ -19,17 +19,17 @@ keywords = []
19
19
  requires-python = "==3.13.*"
20
20
 
21
21
  dependencies = [
22
- "aiohttp[speedups]~=3.12.2",
22
+ "aiohttp[speedups]~=3.12.13",
23
23
  "asyncpg~=0.30.0",
24
24
  "fastapi-users-db-sqlalchemy~=7.0.0",
25
25
  "fastapi-users[sqlalchemy]~=14.0.1",
26
26
  "lxml~=5.4.0",
27
27
  "minio~=7.2.15",
28
- "msgpack~=1.1.0",
28
+ "msgpack~=1.1.1",
29
29
  "msgspec~=0.19.0",
30
- "pydantic[email]~=2.11.5",
31
- "pydantic-settings~=2.9.1",
32
- "python-jose==3.4.0",
30
+ "pydantic[email]~=2.11.7",
31
+ "pydantic-settings~=2.10.0",
32
+ "python-jose==3.5.0",
33
33
  "SQLAlchemy[asyncio]~=2.0.40",
34
34
  "valkey[libvalkey]~=6.1.0",
35
35
  "zeep~=4.3.1"
@@ -40,14 +40,15 @@ dev = [
40
40
  "build",
41
41
  "pip==25.1.1",
42
42
  "pre-commit==4.2.0",
43
- "pyright==1.1.401",
44
- "ruff==0.11.11",
45
- "setuptools==80.8.0",
43
+ "pyright==1.1.402",
44
+ "ruff==0.12.0",
45
+ "setuptools==80.9.0",
46
46
  "setuptools_scm==8.3.1",
47
47
  "wheel==0.45.1",
48
48
  ]
49
49
  testing = [
50
50
  "pytest",
51
+ "pytest-asyncio",
51
52
  "pytest-cov",
52
53
  "pytest-mock"
53
54
  ]
@@ -2,16 +2,18 @@ import dataclasses
2
2
  import json
3
3
  import logging
4
4
  import struct
5
- from _decimal import Decimal
6
5
  from datetime import date, datetime
7
- from typing import Any
6
+ from decimal import Decimal
7
+ from typing import Any, Type, TypeVar
8
8
 
9
9
  from msgspec import msgpack
10
10
  from msgspec.msgpack import Ext, encode
11
+ from pydantic import BaseModel
11
12
 
12
13
  from python3_commons.serializers.json import CustomJSONEncoder
13
14
 
14
15
  logger = logging.getLogger(__name__)
16
+ T = TypeVar('T')
15
17
 
16
18
 
17
19
  def enc_hook(obj: Any) -> Any:
@@ -45,28 +47,44 @@ MSGPACK_DECODER = msgpack.Decoder(ext_hook=ext_hook)
45
47
  MSGPACK_DECODER_NATIVE = msgpack.Decoder()
46
48
 
47
49
 
48
- def serialize_msgpack_native(data) -> bytes:
49
- return encode(data)
50
+ def serialize_msgpack_native(data: Any) -> bytes:
51
+ if isinstance(data, BaseModel):
52
+ data = data.model_dump()
50
53
 
54
+ result = encode(data)
51
55
 
52
- def deserialize_msgpack_native(data: bytes, data_type=None):
56
+ return result
57
+
58
+
59
+ def deserialize_msgpack_native(data: bytes, data_type: Type[T] | None = None) -> T | Any:
53
60
  if data_type:
54
- result = msgpack.decode(data, type=data_type)
61
+ if issubclass(data_type, BaseModel):
62
+ decoded = MSGPACK_DECODER_NATIVE.decode(data)
63
+ result = data_type.model_validate(decoded)
64
+ else:
65
+ result = msgpack.decode(data, type=data_type)
55
66
  else:
56
67
  result = MSGPACK_DECODER_NATIVE.decode(data)
57
68
 
58
69
  return result
59
70
 
60
71
 
61
- def serialize_msgpack(data) -> bytes:
72
+ def serialize_msgpack(data: Any) -> bytes:
73
+ if isinstance(data, BaseModel):
74
+ data = data.model_dump()
75
+
62
76
  result = MSGPACK_ENCODER.encode(data)
63
77
 
64
78
  return result
65
79
 
66
80
 
67
- def deserialize_msgpack(data: bytes, data_type=None):
81
+ def deserialize_msgpack(data: bytes, data_type: Type[T] | None = None) -> T | Any:
68
82
  if data_type:
69
- result = msgpack.decode(data, type=data_type)
83
+ if issubclass(data_type, BaseModel):
84
+ decoded = MSGPACK_DECODER.decode(data)
85
+ result = data_type.model_validate(decoded)
86
+ else:
87
+ result = msgpack.decode(data, type=data_type)
70
88
  else:
71
89
  result = MSGPACK_DECODER.decode(data)
72
90
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.8.32
3
+ Version: 0.8.34
4
4
  Summary: Re-usable Python3 code
5
5
  Author-email: Oleg Korsak <kamikaze.is.waiting.you@gmail.com>
6
6
  License-Expression: GPL-3.0
@@ -12,17 +12,17 @@ Requires-Python: ==3.13.*
12
12
  Description-Content-Type: text/x-rst
13
13
  License-File: LICENSE
14
14
  License-File: AUTHORS.rst
15
- Requires-Dist: aiohttp[speedups]~=3.12.2
15
+ Requires-Dist: aiohttp[speedups]~=3.12.13
16
16
  Requires-Dist: asyncpg~=0.30.0
17
17
  Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
18
18
  Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
19
19
  Requires-Dist: lxml~=5.4.0
20
20
  Requires-Dist: minio~=7.2.15
21
- Requires-Dist: msgpack~=1.1.0
21
+ Requires-Dist: msgpack~=1.1.1
22
22
  Requires-Dist: msgspec~=0.19.0
23
- Requires-Dist: pydantic[email]~=2.11.5
24
- Requires-Dist: pydantic-settings~=2.9.1
25
- Requires-Dist: python-jose==3.4.0
23
+ Requires-Dist: pydantic[email]~=2.11.7
24
+ Requires-Dist: pydantic-settings~=2.10.0
25
+ Requires-Dist: python-jose==3.5.0
26
26
  Requires-Dist: SQLAlchemy[asyncio]~=2.0.40
27
27
  Requires-Dist: valkey[libvalkey]~=6.1.0
28
28
  Requires-Dist: zeep~=4.3.1
@@ -48,6 +48,7 @@ src/python3_commons/serializers/msgpack.py
48
48
  src/python3_commons/serializers/msgspec.py
49
49
  tests/conftest.py
50
50
  tests/test_audit.py
51
+ tests/test_cache.py
51
52
  tests/test_helpers.py
52
53
  tests/test_msgpack.py
53
54
  tests/test_msgspec.py
@@ -1,14 +1,14 @@
1
- aiohttp[speedups]~=3.12.2
1
+ aiohttp[speedups]~=3.12.13
2
2
  asyncpg~=0.30.0
3
3
  fastapi-users-db-sqlalchemy~=7.0.0
4
4
  fastapi-users[sqlalchemy]~=14.0.1
5
5
  lxml~=5.4.0
6
6
  minio~=7.2.15
7
- msgpack~=1.1.0
7
+ msgpack~=1.1.1
8
8
  msgspec~=0.19.0
9
- pydantic[email]~=2.11.5
10
- pydantic-settings~=2.9.1
11
- python-jose==3.4.0
9
+ pydantic[email]~=2.11.7
10
+ pydantic-settings~=2.10.0
11
+ python-jose==3.5.0
12
12
  SQLAlchemy[asyncio]~=2.0.40
13
13
  valkey[libvalkey]~=6.1.0
14
14
  zeep~=4.3.1
@@ -0,0 +1,117 @@
1
+ from dataclasses import dataclass
2
+ from datetime import date, datetime
3
+ from decimal import Decimal
4
+ from uuid import UUID, uuid4
5
+
6
+ import msgspec
7
+ import pytest
8
+ from pydantic import BaseModel
9
+
10
+
11
+ @pytest.fixture
12
+ def data_dict():
13
+ return {
14
+ 'A': 1,
15
+ 'B': 'B',
16
+ 'C': None,
17
+ 'D': datetime(2023, 7, 25, 1, 2, 3),
18
+ 'E': date(2023, 7, 24),
19
+ 'F': Decimal('1.23'),
20
+ }
21
+
22
+
23
+ @dataclass
24
+ class TestData:
25
+ a: int
26
+ b: str
27
+ c: str | None
28
+ d: datetime
29
+ e: date
30
+ f: Decimal
31
+
32
+
33
+ @pytest.fixture
34
+ def data_dataclass():
35
+ return TestData(a=1, b='B', c=None, d=datetime(2023, 7, 25, 1, 2, 3), e=date(2023, 7, 24), f=Decimal('1.23'))
36
+
37
+
38
+ class SubStruc(msgspec.Struct):
39
+ uid: UUID
40
+ name: str
41
+
42
+
43
+ class TestStruct(msgspec.Struct):
44
+ a: int
45
+ b: str
46
+ c: str | None
47
+ d: datetime
48
+ e: date
49
+ f: Decimal
50
+ sub: SubStruc
51
+
52
+
53
+ class PydanticSubStruc(BaseModel):
54
+ uid: UUID
55
+ name: str
56
+
57
+
58
+ class PydanticTestStruct(BaseModel):
59
+ a: int
60
+ b: str
61
+ c: str | None
62
+ d: datetime
63
+ e: date
64
+ f: Decimal
65
+ sub: PydanticSubStruc
66
+
67
+
68
+ @pytest.fixture
69
+ def msgspec_struct() -> TestStruct:
70
+ return TestStruct(
71
+ a=1,
72
+ b='B',
73
+ c=None,
74
+ d=datetime(2023, 7, 25, 1, 2, 3),
75
+ e=date(2023, 7, 24),
76
+ f=Decimal('1.23'),
77
+ sub=SubStruc(uid=uuid4(), name='sub-struct'),
78
+ )
79
+
80
+
81
+ @pytest.fixture
82
+ def pydantic_struct() -> PydanticTestStruct:
83
+ return PydanticTestStruct(
84
+ a=1,
85
+ b='B',
86
+ c=None,
87
+ d=datetime(2023, 7, 25, 1, 2, 3),
88
+ e=date(2023, 7, 24),
89
+ f=Decimal('1.23'),
90
+ sub=PydanticSubStruc(uid=uuid4(), name='sub-struct'),
91
+ )
92
+
93
+
94
+ @pytest.fixture
95
+ def s3_file_objects() -> tuple:
96
+ return (
97
+ (
98
+ 'file_a.txt',
99
+ datetime(2024, 1, 1),
100
+ b'ABCDE',
101
+ ),
102
+ (
103
+ 'file_b.txt',
104
+ datetime(2024, 1, 2),
105
+ b'FGHIJ',
106
+ ),
107
+ (
108
+ 'file_c.txt',
109
+ datetime(2024, 1, 3),
110
+ b'KLMNO',
111
+ ),
112
+ (
113
+ 'file_d.txt',
114
+ datetime(2024, 1, 4),
115
+ b'PQRST',
116
+ ),
117
+ )
@@ -0,0 +1,8 @@
1
+ import pytest
2
+
3
+ from python3_commons import cache
4
+
5
+
6
+ @pytest.mark.asyncio
7
+ async def test_encode_decode_dict_to_msgpack(msgspec_struct):
8
+ await cache.store('test:key', msgspec_struct, 8 * 3600)
@@ -1,4 +1,4 @@
1
- from datetime import datetime, date
1
+ from datetime import date, datetime
2
2
  from decimal import Decimal
3
3
 
4
4
  from python3_commons.serializers import msgpack
@@ -16,8 +16,9 @@ def test_encode_decode_dict_to_msgpack(data_dict):
16
16
  'F': '1.23',
17
17
  }
18
18
  binary_data = msgspec.serialize_msgpack(data_dict)
19
+ deserialized_data = msgspec.deserialize_msgpack(binary_data)
19
20
 
20
- assert msgspec.deserialize_msgpack(binary_data) == data_dict
21
+ assert deserialized_data == expected_result
21
22
 
22
23
 
23
24
  def test_encode_decode_dataclass_to_msgpack(data_dataclass):
@@ -26,18 +27,18 @@ def test_encode_decode_dataclass_to_msgpack(data_dataclass):
26
27
  assert msgspec.deserialize_msgpack(binary_data, data_type=data_dataclass.__class__) == data_dataclass
27
28
 
28
29
 
29
- def test_encode_decode_struct_to_msgpack(data_struct):
30
- binary_data = msgspec.serialize_msgpack(data_struct)
31
- decoded_struct = msgspec.deserialize_msgpack(binary_data, data_struct.__class__)
30
+ def test_encode_decode_struct_to_msgpack(msgspec_struct):
31
+ binary_data = msgspec.serialize_msgpack(msgspec_struct)
32
+ decoded_struct = msgspec.deserialize_msgpack(binary_data, msgspec_struct.__class__)
32
33
 
33
- assert decoded_struct == data_struct
34
+ assert decoded_struct == msgspec_struct
34
35
 
35
36
 
36
- def test_encode_decode_struct_to_msgpack_native(data_struct):
37
- binary_data = msgspec.serialize_msgpack_native(data_struct)
38
- decoded_struct = msgspec.deserialize_msgpack_native(binary_data, data_struct.__class__)
37
+ def test_encode_decode_struct_to_msgpack_native(msgspec_struct):
38
+ binary_data = msgspec.serialize_msgpack_native(msgspec_struct)
39
+ decoded_struct = msgspec.deserialize_msgpack_native(binary_data, msgspec_struct.__class__)
39
40
 
40
- assert decoded_struct == data_struct
41
+ assert decoded_struct == msgspec_struct
41
42
 
42
43
 
43
44
  def test_encode_decode_decimal_to_msgpack():
@@ -54,3 +55,17 @@ def test_encode_decode_str_to_msgpack():
54
55
  decoded_value = msgspec.deserialize_msgpack(binary_data)
55
56
 
56
57
  assert decoded_value == value
58
+
59
+
60
+ def test_encode_decode_pydantic_struct_to_msgpack(pydantic_struct):
61
+ binary_data = msgspec.serialize_msgpack(pydantic_struct)
62
+ decoded_struct = msgspec.deserialize_msgpack(binary_data, pydantic_struct.__class__)
63
+
64
+ assert decoded_struct == pydantic_struct
65
+
66
+
67
+ def test_encode_decode_pydantic_struct_to_msgpack_native(pydantic_struct):
68
+ binary_data = msgspec.serialize_msgpack_native(pydantic_struct)
69
+ decoded_struct = msgspec.deserialize_msgpack_native(binary_data, pydantic_struct.__class__)
70
+
71
+ assert decoded_struct == pydantic_struct