python3-commons 0.9.17__py3-none-any.whl → 0.9.19__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.

Potentially problematic release.


This version of python3-commons might be problematic. Click here for more details.

@@ -1,10 +1,12 @@
1
+ import errno
1
2
  import logging
3
+ from collections.abc import AsyncGenerator, Mapping, Sequence
2
4
  from contextlib import asynccontextmanager
3
5
  from datetime import UTC, datetime
4
6
  from enum import Enum
5
7
  from http import HTTPStatus
6
8
  from json import dumps
7
- from typing import AsyncGenerator, Literal, Mapping, Sequence
9
+ from typing import Literal
8
10
  from uuid import uuid4
9
11
 
10
12
  from aiohttp import ClientResponse, ClientSession, ClientTimeout, client_exceptions
@@ -52,8 +54,8 @@ async def request(
52
54
  date_path = now.strftime('%Y/%m/%d')
53
55
  timestamp = now.strftime('%H%M%S_%f')
54
56
  request_id = str(uuid4())[-12:]
55
- uri_path = uri[:-1] if uri.endswith('/') else uri
56
- uri_path = uri_path[1:] if uri_path.startswith('/') else uri_path
57
+ uri_path = uri.removesuffix('/')
58
+ uri_path = uri_path.removeprefix('/')
57
59
  url = f'{u[:-1] if (u := str(base_url)).endswith("/") else u}{uri}'
58
60
 
59
61
  if audit_name:
@@ -90,15 +92,25 @@ async def request(
90
92
  else:
91
93
  match response.status:
92
94
  case HTTPStatus.UNAUTHORIZED:
93
- raise PermissionError('Unauthorized')
95
+ msg = 'Unauthorized'
96
+
97
+ raise PermissionError(msg)
94
98
  case HTTPStatus.FORBIDDEN:
95
- raise PermissionError('Forbidden')
99
+ msg = 'Forbidden'
100
+
101
+ raise PermissionError(msg)
96
102
  case HTTPStatus.NOT_FOUND:
97
- raise LookupError('Not found')
103
+ msg = 'Not found'
104
+
105
+ raise LookupError(msg)
98
106
  case HTTPStatus.BAD_REQUEST:
99
- raise ValueError('Bad request')
107
+ msg = 'Bad request'
108
+
109
+ raise ValueError(msg)
100
110
  case HTTPStatus.TOO_MANY_REQUESTS:
101
- raise InterruptedError('Too many requests')
111
+ msg = 'Too many requests'
112
+
113
+ raise InterruptedError(msg)
102
114
  case _:
103
115
  response.raise_for_status()
104
116
  else:
@@ -116,13 +128,21 @@ async def request(
116
128
 
117
129
  yield response
118
130
  except client_exceptions.ClientConnectorError as e:
119
- raise ConnectionRefusedError('Cient connection error') from e
131
+ msg = 'Cient connection error'
132
+
133
+ raise ConnectionRefusedError(msg) from e
120
134
  except client_exceptions.ClientOSError as e:
121
- if e.errno == 32:
122
- raise ConnectionResetError('Broken pipe') from e
123
- elif e.errno == 104:
124
- raise ConnectionResetError('Connection reset by peer') from e
135
+ if e.errno == errno.EPIPE:
136
+ msg = 'Broken pipe'
137
+
138
+ raise ConnectionResetError(msg) from e
139
+ elif e.errno == errno.ECONNRESET:
140
+ msg = 'Connection reset by peer'
141
+
142
+ raise ConnectionResetError(msg) from e
125
143
 
126
144
  raise
127
145
  except client_exceptions.ServerDisconnectedError as e:
128
- raise ConnectionResetError('Server disconnected') from e
146
+ msg = 'Server disconnected'
147
+
148
+ raise ConnectionResetError(msg) from e
python3_commons/auth.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
+ from collections.abc import Callable, Coroutine, MutableMapping, Sequence
2
3
  from http import HTTPStatus
3
- from typing import Annotated, Any, Callable, Coroutine, Sequence, Type, TypeVar
4
+ from typing import Annotated, Any, TypeVar
4
5
 
5
6
  import aiohttp
6
7
  import msgspec
@@ -21,10 +22,7 @@ class TokenData(msgspec.Struct):
21
22
 
22
23
 
23
24
  T = TypeVar('T', bound=TokenData)
24
-
25
25
  OIDC_CONFIG_URL = f'{oidc_settings.authority_url}/.well-known/openid-configuration'
26
- _JWKS: dict | None = None
27
-
28
26
  bearer_security = HTTPBearer(auto_error=oidc_settings.enabled)
29
27
 
30
28
 
@@ -32,51 +30,52 @@ async def fetch_openid_config() -> dict:
32
30
  """
33
31
  Fetch the OpenID configuration (including JWKS URI) from OIDC authority.
34
32
  """
35
- async with aiohttp.ClientSession() as session:
36
- async with session.get(OIDC_CONFIG_URL) as response:
37
- if response.status != HTTPStatus.OK:
38
- raise HTTPException(
39
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail='Failed to fetch OpenID configuration'
40
- )
33
+ async with aiohttp.ClientSession() as session, session.get(OIDC_CONFIG_URL) as response:
34
+ if response.status != HTTPStatus.OK:
35
+ raise HTTPException(
36
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail='Failed to fetch OpenID configuration'
37
+ )
41
38
 
42
- return await response.json()
39
+ return await response.json()
43
40
 
44
41
 
45
42
  async def fetch_jwks(jwks_uri: str) -> dict:
46
43
  """
47
44
  Fetch the JSON Web Key Set (JWKS) for validating the token's signature.
48
45
  """
49
- async with aiohttp.ClientSession() as session:
50
- async with session.get(jwks_uri) as response:
51
- if response.status != HTTPStatus.OK:
52
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail='Failed to fetch JWKS')
46
+ async with aiohttp.ClientSession() as session, session.get(jwks_uri) as response:
47
+ if response.status != HTTPStatus.OK:
48
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail='Failed to fetch JWKS')
53
49
 
54
- return await response.json()
50
+ return await response.json()
55
51
 
56
52
 
57
- def get_token_verifier(token_cls: Type[T]) -> Callable[[HTTPAuthorizationCredentials], Coroutine[Any, Any, T | None]]:
53
+ def get_token_verifier[T](
54
+ token_cls: type[T],
55
+ jwks: MutableMapping,
56
+ ) -> Callable[[HTTPAuthorizationCredentials], Coroutine[Any, Any, T | None]]:
58
57
  async def get_verified_token(
59
58
  authorization: Annotated[HTTPAuthorizationCredentials, Depends(bearer_security)],
60
59
  ) -> T | None:
61
60
  """
62
61
  Verify the JWT access token using OIDC authority JWKS.
63
62
  """
64
- global _JWKS
65
-
66
63
  if not oidc_settings.enabled:
67
64
  return None
68
65
 
69
66
  token = authorization.credentials
70
67
 
71
68
  try:
72
- if not _JWKS:
69
+ if not jwks:
73
70
  openid_config = await fetch_openid_config()
74
- _JWKS = await fetch_jwks(openid_config['jwks_uri'])
71
+ _jwks = await fetch_jwks(openid_config['jwks_uri'])
72
+ jwks.clear()
73
+ jwks.update(_jwks)
75
74
 
76
75
  if oidc_settings.client_id:
77
- payload = jwt.decode(token, _JWKS, algorithms=['RS256'], audience=oidc_settings.client_id)
76
+ payload = jwt.decode(token, jwks, algorithms=['RS256'], audience=oidc_settings.client_id)
78
77
  else:
79
- payload = jwt.decode(token, _JWKS, algorithms=['RS256'])
78
+ payload = jwt.decode(token, jwks, algorithms=['RS256'])
80
79
 
81
80
  token_data = token_cls(**payload)
82
81
 
python3_commons/cache.py CHANGED
@@ -1,7 +1,8 @@
1
1
  import logging
2
2
  import socket
3
+ from collections.abc import Mapping, Sequence
3
4
  from platform import platform
4
- from typing import Any, Mapping, Sequence
5
+ from typing import Any
5
6
 
6
7
  import valkey
7
8
  from pydantic import RedisDsn
@@ -34,7 +35,7 @@ class AsyncValkeyClient(metaclass=SingletonMeta):
34
35
 
35
36
  @staticmethod
36
37
  def _get_keepalive_options():
37
- if platform == 'linux' or platform == 'darwin':
38
+ if platform in {'linux', 'darwin'}:
38
39
  return {socket.TCP_KEEPIDLE: 10, socket.TCP_KEEPINTVL: 5, socket.TCP_KEEPCNT: 5}
39
40
  else:
40
41
  return {}
@@ -88,7 +89,7 @@ async def delete(*names: str | bytes | memoryview):
88
89
  await get_valkey_client().delete(*names)
89
90
 
90
91
 
91
- async def store_bytes(name: str, data: bytes, ttl: int = None, if_not_set: bool = False):
92
+ async def store_bytes(name: str, data: bytes, ttl: int = None, *, if_not_set: bool = False):
92
93
  r = get_valkey_client()
93
94
 
94
95
  return await r.set(name, data, ex=ttl, nx=if_not_set)
@@ -100,8 +101,8 @@ async def get_bytes(name: str) -> bytes | None:
100
101
  return await r.get(name)
101
102
 
102
103
 
103
- async def store(name: str, obj: Any, ttl: int = None, if_not_set: bool = False):
104
- return await store_bytes(name, serialize_msgpack_native(obj), ttl, if_not_set)
104
+ async def store(name: str, obj: Any, ttl: int = None, *, if_not_set: bool = False):
105
+ return await store_bytes(name, serialize_msgpack_native(obj), ttl, if_not_set=if_not_set)
105
106
 
106
107
 
107
108
  async def get(name: str, default=None, data_type: Any = None) -> Any:
@@ -1,6 +1,6 @@
1
1
  import contextlib
2
2
  import logging
3
- from typing import AsyncGenerator, Callable, Mapping
3
+ from collections.abc import AsyncGenerator, Callable, Mapping
4
4
 
5
5
  from sqlalchemy import MetaData
6
6
  from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_engine_from_config
@@ -63,8 +63,8 @@ class AsyncSessionManager:
63
63
 
64
64
  return session_maker
65
65
 
66
- def get_async_session(self, name: str) -> Callable[[], AsyncGenerator[AsyncSession, None]]:
67
- async def get_session() -> AsyncGenerator[AsyncSession, None]:
66
+ def get_async_session(self, name: str) -> Callable[[], AsyncGenerator[AsyncSession]]:
67
+ async def get_session() -> AsyncGenerator[AsyncSession]:
68
68
  session_maker = self.get_session_maker(name)
69
69
 
70
70
  async with session_maker() as session:
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import Mapping
2
+ from collections.abc import Mapping
3
3
 
4
4
  import sqlalchemy as sa
5
5
  from sqlalchemy import asc, desc, func
@@ -26,11 +26,12 @@ def get_query(
26
26
  for order_by_col in order_by.split(','):
27
27
  if order_by_col.startswith('-'):
28
28
  direction = desc
29
- order_by_col = order_by_col[1:]
29
+ order_by_col_clean = order_by_col[1:]
30
30
  else:
31
31
  direction = asc
32
+ order_by_col_clean = order_by_col
32
33
 
33
- order_by_cols[order_by_col] = direction
34
+ order_by_cols[order_by_col_clean] = direction
34
35
 
35
36
  order_by_clauses = tuple(
36
37
  direction(columns[order_by_col][0]) for order_by_col, direction in order_by_cols.items()
@@ -54,9 +55,6 @@ def get_query(
54
55
  else:
55
56
  where_parts = None
56
57
 
57
- if where_parts:
58
- where_clause = sa.and_(*where_parts)
59
- else:
60
- where_clause = None
58
+ where_clause = sa.and_(*where_parts) if where_parts else None
61
59
 
62
60
  return where_clause, order_by_clauses
python3_commons/fs.py CHANGED
@@ -1,8 +1,8 @@
1
+ from collections.abc import Generator
1
2
  from pathlib import Path
2
- from typing import Generator
3
3
 
4
4
 
5
- def iter_files(root: Path, recursive: bool = True) -> Generator[Path, None, None]:
5
+ def iter_files(root: Path, *, recursive: bool = True) -> Generator[Path]:
6
6
  for item in root.iterdir():
7
7
  if item.is_file():
8
8
  yield item
@@ -6,11 +6,12 @@ import threading
6
6
  import time
7
7
  from abc import ABCMeta
8
8
  from collections import defaultdict
9
+ from collections.abc import Mapping, Sequence
9
10
  from datetime import date, datetime, timedelta
10
11
  from decimal import ROUND_HALF_UP, Decimal
11
12
  from http.cookies import BaseCookie
12
13
  from json import dumps
13
- from typing import Literal, Mapping, Sequence
14
+ from typing import Literal
14
15
  from urllib.parse import urlencode
15
16
 
16
17
  from python3_commons.serializers.json import CustomJSONEncoder
@@ -34,7 +35,7 @@ class SingletonMeta(ABCMeta):
34
35
  try:
35
36
  return cls.__instances[cls]
36
37
  except KeyError:
37
- instance = super(SingletonMeta, cls).__call__(*args, **kwargs)
38
+ instance = super().__call__(*args, **kwargs)
38
39
  cls.__instances[cls] = instance
39
40
 
40
41
  return instance
@@ -1,16 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- import io
4
3
  import logging
5
4
  from contextlib import asynccontextmanager
6
- from datetime import datetime
7
- from typing import TYPE_CHECKING, AsyncGenerator, Iterable, Mapping, Sequence
5
+ from typing import TYPE_CHECKING
8
6
 
9
7
  import aiobotocore.session
10
- from aiobotocore.response import StreamingBody
11
8
  from botocore.config import Config
12
9
 
13
10
  if TYPE_CHECKING:
11
+ import io
12
+ from collections.abc import AsyncGenerator, Iterable, Mapping, Sequence
13
+ from datetime import datetime
14
+
15
+ from aiobotocore.response import StreamingBody
14
16
  from types_aiobotocore_s3.client import S3Client
15
17
 
16
18
  from python3_commons.conf import S3Settings, s3_settings
@@ -41,14 +43,13 @@ class ObjectStorage(metaclass=SingletonMeta):
41
43
  self._config = config
42
44
 
43
45
  @asynccontextmanager
44
- async def get_client(self) -> AsyncGenerator[S3Client, None]:
46
+ async def get_client(self) -> AsyncGenerator[S3Client]:
45
47
  async with self._session.create_client('s3', **self._config) as client:
46
48
  yield client
47
49
 
48
50
 
49
51
  def get_absolute_path(path: str) -> str:
50
- if path.startswith('/'):
51
- path = path[1:]
52
+ path = path.removeprefix('/')
52
53
 
53
54
  if bucket_root := s3_settings.s3_bucket_root:
54
55
  path = f'{bucket_root[:1] if bucket_root.startswith("/") else bucket_root}/{path}'
@@ -102,7 +103,7 @@ async def get_object(bucket_name: str, path: str) -> bytes:
102
103
  return body
103
104
 
104
105
 
105
- async def list_objects(bucket_name: str, prefix: str, recursive: bool = True) -> AsyncGenerator[Mapping, None]:
106
+ async def list_objects(bucket_name: str, prefix: str, *, recursive: bool = True) -> AsyncGenerator[Mapping]:
106
107
  storage = ObjectStorage(s3_settings)
107
108
 
108
109
  async with storage.get_client() as s3_client:
@@ -117,9 +118,9 @@ async def list_objects(bucket_name: str, prefix: str, recursive: bool = True) ->
117
118
 
118
119
 
119
120
  async def get_object_streams(
120
- bucket_name: str, path: str, recursive: bool = True
121
- ) -> AsyncGenerator[tuple[str, datetime, StreamingBody], None]:
122
- async for obj in list_objects(bucket_name, path, recursive):
121
+ bucket_name: str, path: str, *, recursive: bool = True
122
+ ) -> AsyncGenerator[tuple[str, datetime, StreamingBody]]:
123
+ async for obj in list_objects(bucket_name, path, recursive=recursive):
123
124
  object_name = obj['Key']
124
125
  last_modified = obj['LastModified']
125
126
 
@@ -128,9 +129,9 @@ async def get_object_streams(
128
129
 
129
130
 
130
131
  async def get_objects(
131
- bucket_name: str, path: str, recursive: bool = True
132
- ) -> AsyncGenerator[tuple[str, datetime, bytes], None]:
133
- async for object_name, last_modified, stream in get_object_streams(bucket_name, path, recursive):
132
+ bucket_name: str, path: str, *, recursive: bool = True
133
+ ) -> AsyncGenerator[tuple[str, datetime, bytes]]:
134
+ async for object_name, last_modified, stream in get_object_streams(bucket_name, path, recursive=recursive):
134
135
  data = await stream.read()
135
136
 
136
137
  yield object_name, last_modified, data
@@ -155,13 +156,12 @@ async def remove_objects(
155
156
  storage = ObjectStorage(s3_settings)
156
157
 
157
158
  async with storage.get_client() as s3_client:
158
- objects_to_delete = []
159
-
160
159
  if prefix:
161
- async for obj in list_objects(bucket_name, prefix, recursive=True):
162
- objects_to_delete.append({'Key': obj['Key']})
160
+ objects_to_delete = tuple(
161
+ {'Key': obj['Key']} async for obj in list_objects(bucket_name, prefix, recursive=True)
162
+ )
163
163
  elif object_names:
164
- objects_to_delete = [{'Key': name} for name in object_names]
164
+ objects_to_delete = tuple({'Key': name} for name in object_names)
165
165
  else:
166
166
  return None
167
167
 
@@ -0,0 +1,8 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class ExtendedType(IntEnum):
5
+ DECIMAL = 1
6
+ DATETIME = 2
7
+ DATE = 3
8
+ DATACLASS = 4
@@ -10,11 +10,9 @@ from typing import Any
10
10
  class CustomJSONEncoder(json.JSONEncoder):
11
11
  def default(self, o) -> Any:
12
12
  try:
13
- return super(CustomJSONEncoder, self).default(o)
13
+ return super().default(o)
14
14
  except TypeError:
15
- if isinstance(o, datetime):
16
- return o.isoformat()
17
- elif isinstance(o, date):
15
+ if isinstance(o, (datetime, date)):
18
16
  return o.isoformat()
19
17
  elif isinstance(o, bytes):
20
18
  return base64.b64encode(o).decode('ascii')
@@ -7,6 +7,7 @@ from decimal import Decimal
7
7
  import msgpack
8
8
  from msgpack import ExtType
9
9
 
10
+ from python3_commons.serializers.common import ExtendedType
10
11
  from python3_commons.serializers.json import CustomJSONEncoder
11
12
 
12
13
  logger = logging.getLogger(__name__)
@@ -14,26 +15,27 @@ logger = logging.getLogger(__name__)
14
15
 
15
16
  def msgpack_encoder(obj):
16
17
  if isinstance(obj, Decimal):
17
- return ExtType(1, str(obj).encode())
18
+ return ExtType(ExtendedType.DECIMAL, str(obj).encode())
18
19
  elif isinstance(obj, datetime):
19
- return ExtType(2, obj.isoformat().encode())
20
+ return ExtType(ExtendedType.DATETIME, obj.isoformat().encode())
20
21
  elif isinstance(obj, date):
21
- return ExtType(3, obj.isoformat().encode())
22
+ return ExtType(ExtendedType.DATE, obj.isoformat().encode())
22
23
  elif dataclasses.is_dataclass(obj):
23
- return ExtType(4, json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode())
24
+ return ExtType(ExtendedType.DATACLASS, json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode())
24
25
 
25
26
  return f'no encoder for {obj}'
26
27
 
27
28
 
28
29
  def msgpack_decoder(code, data):
29
- if code == 1:
30
- return Decimal(data.decode())
31
- elif code == 2:
32
- return datetime.fromisoformat(data.decode())
33
- elif code == 3:
34
- return date.fromisoformat(data.decode())
35
- elif code == 4:
36
- return json.loads(data)
30
+ match code:
31
+ case ExtendedType.DECIMAL:
32
+ return Decimal(data.decode())
33
+ case ExtendedType.DATETIME:
34
+ return datetime.fromisoformat(data.decode())
35
+ case ExtendedType.DATE:
36
+ return date.fromisoformat(data.decode())
37
+ case ExtendedType.DATACLASS:
38
+ return json.loads(data)
37
39
 
38
40
  return f'no decoder for type {code}'
39
41
 
@@ -4,12 +4,13 @@ import logging
4
4
  import struct
5
5
  from datetime import date, datetime
6
6
  from decimal import Decimal
7
- from typing import Any, Type, TypeVar
7
+ from typing import Any, TypeVar
8
8
 
9
9
  from msgspec import msgpack
10
10
  from msgspec.msgpack import Ext, encode
11
11
  from pydantic import BaseModel
12
12
 
13
+ from python3_commons.serializers.common import ExtendedType
13
14
  from python3_commons.serializers.json import CustomJSONEncoder
14
15
 
15
16
  logger = logging.getLogger(__name__)
@@ -18,28 +19,36 @@ T = TypeVar('T')
18
19
 
19
20
  def enc_hook(obj: Any) -> Any:
20
21
  if isinstance(obj, Decimal):
21
- return Ext(1, struct.pack('b', str(obj).encode()))
22
+ return Ext(ExtendedType.DECIMAL, struct.pack('b', str(obj).encode()))
22
23
  elif isinstance(obj, datetime):
23
- return Ext(2, struct.pack('b', obj.isoformat().encode()))
24
+ return Ext(ExtendedType.DATETIME, struct.pack('b', obj.isoformat().encode()))
24
25
  elif isinstance(obj, date):
25
- return Ext(3, struct.pack('b', obj.isoformat().encode()))
26
+ return Ext(ExtendedType.DATE, struct.pack('b', obj.isoformat().encode()))
26
27
  elif dataclasses.is_dataclass(obj):
27
- return Ext(4, struct.pack('b', json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode()))
28
+ return Ext(
29
+ ExtendedType.DATACLASS,
30
+ struct.pack('b', json.dumps(dataclasses.asdict(obj), cls=CustomJSONEncoder).encode()),
31
+ )
28
32
 
29
- raise NotImplementedError(f'Objects of type {type(obj)} are not supported')
33
+ msg = f'Objects of type {type(obj)} are not supported'
34
+
35
+ raise NotImplementedError(msg)
30
36
 
31
37
 
32
38
  def ext_hook(code: int, data: memoryview) -> Any:
33
- if code == 1:
34
- return Decimal(data.tobytes().decode())
35
- elif code == 2:
36
- return datetime.fromisoformat(data.tobytes().decode())
37
- elif code == 3:
38
- return date.fromisoformat(data.tobytes().decode())
39
- elif code == 4:
40
- return json.loads(data.tobytes())
39
+ match code:
40
+ case ExtendedType.DECIMAL:
41
+ return Decimal(data.tobytes().decode())
42
+ case ExtendedType.DATETIME:
43
+ return datetime.fromisoformat(data.tobytes().decode())
44
+ case ExtendedType.DATE:
45
+ return date.fromisoformat(data.tobytes().decode())
46
+ case ExtendedType.DATACLASS:
47
+ return json.loads(data.tobytes())
48
+ case _:
49
+ msg = f'Extension type code {code} is not supported'
41
50
 
42
- raise NotImplementedError(f'Extension type code {code} is not supported')
51
+ raise NotImplementedError(msg)
43
52
 
44
53
 
45
54
  MSGPACK_ENCODER = msgpack.Encoder(enc_hook=enc_hook)
@@ -56,7 +65,7 @@ def serialize_msgpack_native(data: Any) -> bytes:
56
65
  return result
57
66
 
58
67
 
59
- def deserialize_msgpack_native(data: bytes, data_type: Type[T] | None = None) -> T | Any:
68
+ def deserialize_msgpack_native[T](data: bytes, data_type: type[T] | None = None) -> T | Any:
60
69
  if data_type:
61
70
  if issubclass(data_type, BaseModel):
62
71
  decoded = MSGPACK_DECODER_NATIVE.decode(data)
@@ -78,7 +87,7 @@ def serialize_msgpack(data: Any) -> bytes:
78
87
  return result
79
88
 
80
89
 
81
- def deserialize_msgpack(data: bytes, data_type: Type[T] | None = None) -> T | Any:
90
+ def deserialize_msgpack[T](data: bytes, data_type: type[T] | None = None) -> T | Any:
82
91
  if data_type:
83
92
  if issubclass(data_type, BaseModel):
84
93
  decoded = MSGPACK_DECODER.decode(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python3-commons
3
- Version: 0.9.17
3
+ Version: 0.9.19
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
@@ -17,15 +17,15 @@ Requires-Dist: aiohttp[speedups]~=3.12.15
17
17
  Requires-Dist: asyncpg~=0.30.0
18
18
  Requires-Dist: fastapi-users-db-sqlalchemy~=7.0.0
19
19
  Requires-Dist: fastapi-users[sqlalchemy]~=14.0.1
20
- Requires-Dist: lxml~=6.0.1
20
+ Requires-Dist: lxml~=6.0.2
21
21
  Requires-Dist: msgpack~=1.1.1
22
22
  Requires-Dist: msgspec~=0.19.0
23
- Requires-Dist: pydantic[email]~=2.11.7
23
+ Requires-Dist: pydantic[email]~=2.11.9
24
24
  Requires-Dist: pydantic-settings~=2.10.1
25
25
  Requires-Dist: python-jose==3.5.0
26
26
  Requires-Dist: SQLAlchemy[asyncio]~=2.0.43
27
27
  Requires-Dist: valkey[libvalkey]~=6.1.1
28
- Requires-Dist: zeep~=4.3.1
28
+ Requires-Dist: zeep~=4.3.2
29
29
  Dynamic: license-file
30
30
 
31
31
  Re-usable Python3 code
@@ -0,0 +1,30 @@
1
+ python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
2
+ python3_commons/api_client.py,sha256=0PE8iYW5zq7n89veC6afSkwj_fzD_nldBc5wdXyg5jo,5266
3
+ python3_commons/audit.py,sha256=p4KRKt0ogkHhJSulg6j5GU-JKBBE903H2c0nuW16GtM,6083
4
+ python3_commons/auth.py,sha256=oc49EmKODXeZOl8oR1w4kL3cqf4J0f8W1ni9ETcFHA8,2936
5
+ python3_commons/cache.py,sha256=nn296-96Vp-Zx0-YDby4mNpSaixhjokOK0Kp82BXy3w,7757
6
+ python3_commons/conf.py,sha256=GOvR1oYss6AKnNlyj9JbT0mSrfQFrnzmgm0IiGVahho,2428
7
+ python3_commons/fs.py,sha256=dn8ZcwsQf9xcAEg6neoxLN6IzJbWpprfm8wV8S55BL0,337
8
+ python3_commons/helpers.py,sha256=WTd-uLxL8JgJncMYVQ5IboRQ-YKnYXxKkzNHlycKN-8,3939
9
+ python3_commons/object_storage.py,sha256=xBKetm-04HVxttz0B8p55cteE_WPXTf9pKF5IpQk9XQ,6561
10
+ python3_commons/permissions.py,sha256=bhjTp-tq-oaTGFMHNnSBlcVX5XQCTL0nWcu6SdPEAB4,1555
11
+ python3_commons/db/__init__.py,sha256=CiGlUcpb85OIEBXLOwqqZNukr7PVsCwdi-Evhqkr-J4,2944
12
+ python3_commons/db/helpers.py,sha256=n56yYCE0fvzvU7nL1936NfZhbaQmvfumzRsGimBlNV4,1776
13
+ python3_commons/db/models/__init__.py,sha256=zjZCf0DNDkqmPZ49quJ6KZohtKH87viI_ijDG3E0PVE,554
14
+ python3_commons/db/models/auth.py,sha256=NMHirujigpaRR0Bhhe2gzy8Q8PPABuaA-D8ZY7aaqeE,1177
15
+ python3_commons/db/models/common.py,sha256=nRLQVi7Y0SsXo3qMIwQX6GuDO9kHnlma4O_mYXQVtHQ,1512
16
+ python3_commons/db/models/rbac.py,sha256=BIB7nJXQkCes0XA-fg-oCHP6YU0_rXIm29O73j4pNUg,3160
17
+ python3_commons/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ python3_commons/log/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
19
+ python3_commons/log/formatters.py,sha256=p2AtZD4Axp3Em0e9gWzW8U_yOR5entD7xn7Edvc-IuM,719
20
+ python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ python3_commons/serializers/common.py,sha256=VkA7C6wODvHk0QBXVX_x2JieDstihx3U__UFbTYf654,120
22
+ python3_commons/serializers/json.py,sha256=Dae0gouk9jiUOkmh8z1AcTIHD52OgBn2aXK9JLKMMms,718
23
+ python3_commons/serializers/msgpack.py,sha256=AwzBSUdbdq8yYdGzmEsiWw0bnL9XRQFa1Vh-nt2s56k,1499
24
+ python3_commons/serializers/msgspec.py,sha256=yp3LvsxDUTIv__3AaLed5ddZ0rHm7BYqmrrehVAJb14,3000
25
+ python3_commons-0.9.19.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
26
+ python3_commons-0.9.19.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
27
+ python3_commons-0.9.19.dist-info/METADATA,sha256=Jr8y3hZV5nmpL6hpJcioxUv94-Eytwou4vhv7O7c3Fc,1134
28
+ python3_commons-0.9.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ python3_commons-0.9.19.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
30
+ python3_commons-0.9.19.dist-info/RECORD,,
@@ -1,29 +0,0 @@
1
- python3_commons/__init__.py,sha256=0KgaYU46H_IMKn-BuasoRN3C4Hi45KlkHHoPbU9cwiA,189
2
- python3_commons/api_client.py,sha256=vIWt6QIbHw_x-1yKhXyJEN3Aj22KMsZwJXMsdWR7oaQ,4970
3
- python3_commons/audit.py,sha256=p4KRKt0ogkHhJSulg6j5GU-JKBBE903H2c0nuW16GtM,6083
4
- python3_commons/auth.py,sha256=fINE7zeq-oaEk2lwkdP1KOhfCpcIBaC8P9UzXQI37J0,2922
5
- python3_commons/cache.py,sha256=lf27LTD4Z9Iqi5GaK8jH8UC0cL9sHH8wicZ88YDp6Mg,7725
6
- python3_commons/conf.py,sha256=GOvR1oYss6AKnNlyj9JbT0mSrfQFrnzmgm0IiGVahho,2428
7
- python3_commons/fs.py,sha256=wfLjybXndwLqNlOxTpm_HRJnuTcC4wbrHEOaEeCo9Wc,337
8
- python3_commons/helpers.py,sha256=9m9Q_ImXzOsKmt7ObLUhd11vnGfwUH_KO4IwLaTMrFA,3930
9
- python3_commons/object_storage.py,sha256=6jESNzI__r9wJdTWJQRXhpbdmC2QrZVUoZyvetfQNwY,6544
10
- python3_commons/permissions.py,sha256=bhjTp-tq-oaTGFMHNnSBlcVX5XQCTL0nWcu6SdPEAB4,1555
11
- python3_commons/db/__init__.py,sha256=5nArsGm17e-pelpOwAeBKy2n_Py20XqklZsNgkcJ-DQ,2947
12
- python3_commons/db/helpers.py,sha256=PY0h08aLiGx-J54wmP3GHPCgGCcLd60rayAUnR3aWdI,1742
13
- python3_commons/db/models/__init__.py,sha256=zjZCf0DNDkqmPZ49quJ6KZohtKH87viI_ijDG3E0PVE,554
14
- python3_commons/db/models/auth.py,sha256=NMHirujigpaRR0Bhhe2gzy8Q8PPABuaA-D8ZY7aaqeE,1177
15
- python3_commons/db/models/common.py,sha256=nRLQVi7Y0SsXo3qMIwQX6GuDO9kHnlma4O_mYXQVtHQ,1512
16
- python3_commons/db/models/rbac.py,sha256=BIB7nJXQkCes0XA-fg-oCHP6YU0_rXIm29O73j4pNUg,3160
17
- python3_commons/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- python3_commons/log/filters.py,sha256=fuyjXZAUm-i2MNrxvFYag8F8Rr27x8W8MdV3ke6miSs,175
19
- python3_commons/log/formatters.py,sha256=p2AtZD4Axp3Em0e9gWzW8U_yOR5entD7xn7Edvc-IuM,719
20
- python3_commons/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- python3_commons/serializers/json.py,sha256=91UaXLGKGj0yPyrnuMeNrkG2GuPUgcgAsmIokUgEwpU,808
22
- python3_commons/serializers/msgpack.py,sha256=WrvaPE187shSK8zkH4UHHMimEZNMv9RaDSwsBE2HlCw,1269
23
- python3_commons/serializers/msgspec.py,sha256=0AliXlEl5sewi0UENjI8St5ZScXE5DNRERKzqWKy2Ps,2674
24
- python3_commons-0.9.17.dist-info/licenses/AUTHORS.rst,sha256=3R9JnfjfjH5RoPWOeqKFJgxVShSSfzQPIrEr1nxIo9Q,90
25
- python3_commons-0.9.17.dist-info/licenses/LICENSE,sha256=xxILuojHm4fKQOrMHPSslbyy6WuKAN2RiG74HbrYfzM,34575
26
- python3_commons-0.9.17.dist-info/METADATA,sha256=_Euxly2fSHCOTstV3gicOHVVui1Oq3KfrmVq3im5RyE,1134
27
- python3_commons-0.9.17.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
28
- python3_commons-0.9.17.dist-info/top_level.txt,sha256=lJI6sCBf68eUHzupCnn2dzG10lH3jJKTWM_hrN1cQ7M,16
29
- python3_commons-0.9.17.dist-info/RECORD,,