usso 0.28.25__py3-none-any.whl → 0.28.27__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.
usso/api_key.py CHANGED
@@ -10,7 +10,7 @@ from .user import UserData
10
10
  logger = logging.getLogger("usso")
11
11
 
12
12
 
13
- def _handle_exception(error_type: str, **kwargs):
13
+ def _handle_exception(error_type: str, **kwargs: dict) -> None:
14
14
  """Handle API key related exceptions."""
15
15
  if kwargs.get("raise_exception", True):
16
16
  raise USSOException(
@@ -20,7 +20,7 @@ def _handle_exception(error_type: str, **kwargs):
20
20
 
21
21
 
22
22
  @cachetools.func.ttl_cache(maxsize=128, ttl=60)
23
- def fetch_api_key_data(api_key_verify_url: str, api_key: str):
23
+ def fetch_api_key_data(api_key_verify_url: str, api_key: str) -> UserData:
24
24
  """Fetch user data using an API key.
25
25
 
26
26
  Args:
usso/authorization.py CHANGED
@@ -26,6 +26,9 @@ def parse_scope(scope: str) -> tuple[str, list[str], dict[str, str]]:
26
26
  "*:*" ->
27
27
  ("*", ["*"], {})
28
28
 
29
+ "media//files" ->
30
+ ("", ["media", "*", "files"], {})
31
+
29
32
  Returns:
30
33
  - action: str (could be empty string if no scheme present)
31
34
  - path_parts: list[str]
@@ -47,78 +50,58 @@ def parse_scope(scope: str) -> tuple[str, list[str], dict[str, str]]:
47
50
  query = scope[question_idx + 1 :]
48
51
  filters = {k: v[0] for k, v in parse_qs(query).items()}
49
52
  resource_path_parts = resource_path.split("/") if resource_path else ["*"]
50
- return action, resource_path_parts, filters
53
+ return action, [rp or "*" for rp in resource_path_parts], filters
51
54
 
52
55
 
53
- def is_path_match(
54
- user_path: list[str] | str,
55
- requested_path: list[str] | str,
56
- strict: bool = False,
57
- ) -> bool:
58
- """
59
- Match resource paths from right to left, supporting wildcards (*).
60
-
61
- Rules:
62
- - The final resource name must match exactly or via fnmatch.
63
- - Upper-level path parts are matched from right to left.
64
- - Wildcards are allowed in any part.
65
-
66
- Examples = [
67
- ("files", "files", True),
68
- ("file-manager/files", "files", True),
69
- ("media/file-manager/files", "files", True),
70
- ("media//files", "files", True),
71
- ("media//files", "file-manager/files", True),
72
- ("files", "file-manager/files", True),
73
- ("*/files", "file-manager/files", True),
74
- ("*//files", "file-manager/files", True),
75
- ("//files", "file-manager/files", True),
76
- ("//files", "media/file-manager/files", True),
77
- ("media//files", "media/file-manager/files", True),
78
- ("media/files/*", "media/files/transactions", True),
79
- ("*/*/transactions", "media/files/transactions", True),
80
- ("media/*/transactions", "media/images/transactions", True),
81
- ("media//files", "media/files", True), # attention
82
-
83
- ("files", "file", False),
84
- ("files", "files/transactions", False),
85
- ("files", "media/files/transactions", False),
86
- ("media/files", "media/files/transactions", False),
87
- ]
88
- """
89
- if isinstance(user_path, str):
90
- user_parts = user_path.split("/")
91
- elif isinstance(user_path, list):
92
- user_parts = user_path
56
+ def _normalize_path(path: list[str] | str) -> list[str]:
57
+ if isinstance(path, str):
58
+ return path.split("/")
59
+ elif isinstance(path, list):
60
+ return path
93
61
  else:
94
- raise ValueError(f"Invalid path type: {type(user_path)}")
62
+ raise ValueError(f"Invalid path type: {type(path)}")
95
63
 
96
- if isinstance(requested_path, str):
97
- req_parts = requested_path.split("/")
98
- elif isinstance(requested_path, list):
99
- req_parts = requested_path
100
- else:
101
- raise ValueError(f"Invalid path type: {type(requested_path)}")
102
64
 
65
+ def _match_path_parts(
66
+ user_parts: list[str], req_parts: list[str], strict: bool
67
+ ) -> bool:
68
+ wildcard_found = False
103
69
  # Match resource name (rightmost)
104
70
  if not fnmatch.fnmatch(req_parts[-1], user_parts[-1]):
105
71
  return False
106
-
72
+ if "*" in user_parts[-1]:
73
+ wildcard_found = True
107
74
  # Match rest of the path from right to left
108
75
  user_path_parts = user_parts[:-1]
109
76
  req_path_parts = req_parts[:-1]
110
-
111
77
  for u, r in zip(
112
- reversed(user_path_parts),
113
- reversed(req_path_parts),
114
- strict=strict,
78
+ reversed(user_path_parts), reversed(req_path_parts), strict=strict
115
79
  ):
116
80
  if r and u and r != "*" and not fnmatch.fnmatch(r, u):
117
81
  return False
118
-
82
+ if "*" in u:
83
+ wildcard_found = True
84
+ offset = len(user_path_parts) - len(req_path_parts)
85
+ if offset > 0 and wildcard_found:
86
+ for u in user_path_parts[:offset]:
87
+ if u != "*":
88
+ return False
119
89
  return True
120
90
 
121
91
 
92
+ def is_path_match(
93
+ user_path: list[str] | str,
94
+ requested_path: list[str] | str,
95
+ strict: bool = False,
96
+ ) -> bool:
97
+ """
98
+ Match resource paths from right to left, supporting wildcards (*).
99
+ """
100
+ user_parts = _normalize_path(user_path)
101
+ req_parts = _normalize_path(requested_path)
102
+ return _match_path_parts(user_parts, req_parts, strict)
103
+
104
+
122
105
  def is_filter_match(user_filters: dict, requested_filters: dict) -> bool:
123
106
  """All user filters must match requested filters."""
124
107
  for k, v in user_filters.items():
@@ -209,7 +192,7 @@ def is_authorized(
209
192
  if not is_path_match(user_path, requested_path, strict=strict):
210
193
  return False
211
194
 
212
- if not is_filter_match(user_filters, reuested_filter):
195
+ if not is_filter_match(user_filters, reuested_filter or {}):
213
196
  return False
214
197
 
215
198
  if requested_action:
@@ -243,15 +226,15 @@ def check_access(
243
226
  if isinstance(filters, dict):
244
227
  filters = [{k: v} for k, v in filters.items()]
245
228
  elif filters is None:
246
- filters = ["*"]
229
+ filters = [{}]
247
230
 
248
231
  for scope in user_scopes:
249
- for filter in filters:
232
+ for filt in filters:
250
233
  if is_authorized(
251
234
  user_scope=scope,
252
235
  requested_path=resource_path,
253
236
  requested_action=action,
254
- reuested_filter=filter,
237
+ reuested_filter=filt,
255
238
  strict=strict,
256
239
  ):
257
240
  return True
usso/client.py CHANGED
@@ -22,7 +22,7 @@ class UssoAuth:
22
22
  self,
23
23
  *,
24
24
  jwt_config: AvailableJwtConfigs | None = None,
25
- ):
25
+ ) -> None:
26
26
  """Initialize the USSO authentication client.
27
27
 
28
28
  Args:
@@ -38,7 +38,7 @@ class UssoAuth:
38
38
  *,
39
39
  expected_token_type: str | None = "access",
40
40
  raise_exception: bool = True,
41
- **kwargs,
41
+ **kwargs: dict,
42
42
  ) -> UserData | None:
43
43
  """Get user data from a JWT token.
44
44
 
usso/config.py CHANGED
@@ -13,7 +13,7 @@ class HeaderConfig(BaseModel):
13
13
  name: str = "usso_access_token"
14
14
 
15
15
  @model_validator(mode="before")
16
- def validate_header(cls, data: dict):
16
+ def validate_header(cls, data: dict) -> dict:
17
17
  if data.get("type") == "Authorization" and not data.get("name"):
18
18
  data["name"] = "Bearer"
19
19
  elif data.get("type") == "Cookie":
@@ -22,10 +22,10 @@ class HeaderConfig(BaseModel):
22
22
  data["name"] = data.get("name", "x-usso-access-token")
23
23
  return data
24
24
 
25
- def __hash__(self):
25
+ def __hash__(self) -> int:
26
26
  return hash(self.model_dump_json())
27
27
 
28
- def get_key(self, request) -> str | None:
28
+ def get_key(self, request: object) -> str | None: # type: ignore
29
29
  headers: dict[str, Any] = getattr(request, "headers", {})
30
30
  cookies: dict[str, str] = getattr(
31
31
  request, "cookies", headers.get("Cookie", {})
@@ -54,18 +54,18 @@ class AuthConfig(usso_jwt.config.JWTConfig):
54
54
  jwt_header: HeaderConfig | None = HeaderConfig()
55
55
  static_api_keys: list[str] | None = None
56
56
 
57
- def get_api_key(self, request) -> str | None:
57
+ def get_api_key(self, request: object) -> str | None:
58
58
  if self.api_key_header:
59
59
  return self.api_key_header.get_key(request)
60
60
  return None
61
61
 
62
- def get_jwt(self, request) -> str | None:
62
+ def get_jwt(self, request: object) -> str | None:
63
63
  if self.jwt_header:
64
64
  return self.jwt_header.get_key(request)
65
65
  return None
66
66
 
67
67
  def verify_token(
68
- self, token: str, *, raise_exception: bool = True, **kwargs
68
+ self, token: str, *, raise_exception: bool = True, **kwargs: dict
69
69
  ) -> bool:
70
70
  from usso_jwt import exceptions as jwt_exceptions
71
71
  from usso_jwt import schemas
usso/exceptions.py CHANGED
@@ -14,30 +14,44 @@ error_messages = {
14
14
 
15
15
  class USSOException(Exception):
16
16
  def __init__(
17
- self, status_code: int, error: str, message: dict | None = None
18
- ):
17
+ self,
18
+ status_code: int,
19
+ error: str,
20
+ detail: str | None = None,
21
+ message: dict | None = None,
22
+ **kwargs: dict,
23
+ ) -> None:
19
24
  self.status_code = status_code
20
25
  self.error = error
21
- self.message = message
26
+ msg: dict = {}
22
27
  if message is None:
23
- self.message = error_messages[error]
24
- super().__init__(message)
28
+ if detail:
29
+ msg["en"] = detail
30
+ else:
31
+ msg["en"] = error_messages.get(error, error)
32
+ else:
33
+ msg = message
34
+
35
+ self.message = msg
36
+ self.detail = detail or str(self.message)
37
+ self.data = kwargs
38
+ super().__init__(detail)
25
39
 
26
40
 
27
41
  class PermissionDenied(USSOException):
28
42
  def __init__(
29
43
  self,
30
44
  error: str = "permission_denied",
31
- message: dict = None,
32
- detail: str = None,
33
- **kwargs,
34
- ):
45
+ detail: str | None = None,
46
+ message: dict | None = None,
47
+ **kwargs: dict,
48
+ ) -> None:
35
49
  super().__init__(
36
50
  403, error=error, message=message, detail=detail, **kwargs
37
51
  )
38
52
 
39
53
 
40
- def _handle_exception(error_type: str, **kwargs):
54
+ def _handle_exception(error_type: str, **kwargs: dict) -> None:
41
55
  """Handle JWT-related exceptions."""
42
56
  if kwargs.get("raise_exception", True):
43
57
  raise USSOException(
@@ -17,7 +17,7 @@ class USSOAuthenticationMiddleware(MiddlewareMixin):
17
17
  def jwt_config(self) -> AuthConfig:
18
18
  return settings.USSO_JWT_CONFIG
19
19
 
20
- def process_request(self, request: HttpRequest):
20
+ def process_request(self, request: HttpRequest) -> None:
21
21
  """
22
22
  Middleware to authenticate users by JWT token and create or
23
23
  return a user in the database.
@@ -16,7 +16,7 @@ class USSOAuthentication(UssoAuth):
16
16
  jwt_config: AvailableJwtConfigs | None = None,
17
17
  raise_exception: bool = True,
18
18
  expected_token_type: str = "access",
19
- ):
19
+ ) -> None:
20
20
  if jwt_config is None:
21
21
  jwt_config = AuthConfig()
22
22
 
@@ -4,10 +4,17 @@ from fastapi.responses import JSONResponse
4
4
  from ...exceptions import USSOException
5
5
 
6
6
 
7
- async def usso_exception_handler(request: Request, exc: USSOException):
7
+ async def usso_exception_handler(
8
+ request: Request, exc: USSOException
9
+ ) -> JSONResponse:
8
10
  return JSONResponse(
9
11
  status_code=exc.status_code,
10
- content={"message": exc.message, "error": exc.error},
12
+ content={
13
+ "message": exc.message,
14
+ "error": exc.error,
15
+ "detail": exc.detail,
16
+ **exc.data,
17
+ },
11
18
  )
12
19
 
13
20
 
@@ -17,8 +17,9 @@ class AsyncUssoSession(httpx.AsyncClient, BaseUssoSession):
17
17
  usso_api_key: str | None = os.getenv("USSO_ADMIN_API_KEY"),
18
18
  user_id: str | None = None,
19
19
  client: "AsyncUssoSession" = None,
20
- ):
21
- httpx.AsyncClient.__init__(self)
20
+ **kwargs: dict,
21
+ ) -> None:
22
+ httpx.AsyncClient.__init__(self, **kwargs)
22
23
  BaseUssoSession.__init__(
23
24
  self,
24
25
  usso_base_url=usso_base_url,
@@ -100,7 +101,7 @@ class AsyncUssoSession(httpx.AsyncClient, BaseUssoSession):
100
101
  )
101
102
  return self._handle_refresh_response(response)
102
103
 
103
- async def get_session(self):
104
+ async def get_session(self) -> "AsyncUssoSession":
104
105
  if hasattr(self, "api_key") and self.api_key:
105
106
  return self
106
107
 
@@ -108,6 +109,8 @@ class AsyncUssoSession(httpx.AsyncClient, BaseUssoSession):
108
109
  await self._refresh()
109
110
  return self
110
111
 
111
- async def _request(self, method: str, url: str, **kwargs):
112
+ async def _request(
113
+ self, method: str, url: str, **kwargs: dict
114
+ ) -> httpx.Response:
112
115
  session = await self.get_session()
113
116
  return await session.request(method, url, **kwargs)
@@ -14,7 +14,7 @@ class BaseUssoSession:
14
14
  app_secret: str | None = None,
15
15
  usso_url: str = "https://sso.usso.io",
16
16
  client: Optional["BaseUssoSession"] = None,
17
- ):
17
+ ) -> None:
18
18
  if client:
19
19
  self.copy_attributes_from(client)
20
20
  return
@@ -54,7 +54,7 @@ class BaseUssoSession:
54
54
  self.access_token = None
55
55
  self.headers = getattr(self, "headers", {})
56
56
 
57
- def copy_attributes_from(self, client: "BaseUssoSession"):
57
+ def copy_attributes_from(self, client: "BaseUssoSession") -> None:
58
58
  self.usso_url = client.usso_url
59
59
  self.usso_refresh_url = client.usso_refresh_url
60
60
  self._refresh_token = client._refresh_token
@@ -64,7 +64,7 @@ class BaseUssoSession:
64
64
  self.headers = client.headers.copy()
65
65
 
66
66
  @property
67
- def refresh_token(self):
67
+ def refresh_token(self) -> JWT:
68
68
  if (
69
69
  self._refresh_token
70
70
  and self._refresh_token.verify( # noqa: W503
usso/session/session.py CHANGED
@@ -14,8 +14,8 @@ class UssoSession(httpx.Client, BaseUssoSession):
14
14
  refresh_token: str | None = os.getenv("USSO_REFRESH_TOKEN"),
15
15
  usso_url: str | None = os.getenv("USSO_URL"),
16
16
  client: "UssoSession" = None,
17
- **kwargs,
18
- ):
17
+ **kwargs: dict,
18
+ ) -> None:
19
19
  httpx.Client.__init__(self, **kwargs)
20
20
 
21
21
  BaseUssoSession.__init__(
@@ -28,7 +28,7 @@ class UssoSession(httpx.Client, BaseUssoSession):
28
28
  if not self.api_key:
29
29
  self._refresh()
30
30
 
31
- def _refresh(self):
31
+ def _refresh(self) -> dict:
32
32
  assert self.refresh_token, "refresh_token is required"
33
33
 
34
34
  response = httpx.post(
@@ -43,7 +43,7 @@ class UssoSession(httpx.Client, BaseUssoSession):
43
43
  self.headers.update({"Authorization": f"Bearer {self.access_token}"})
44
44
  return response.json()
45
45
 
46
- def get_session(self):
46
+ def get_session(self) -> "UssoSession":
47
47
  if self.api_key:
48
48
  return self
49
49
 
@@ -51,6 +51,8 @@ class UssoSession(httpx.Client, BaseUssoSession):
51
51
  self._refresh()
52
52
  return self
53
53
 
54
- def _request(self, method: str, url: str, **kwargs):
54
+ def _request(
55
+ self, method: str, url: str, **kwargs: dict
56
+ ) -> httpx.Response:
55
57
  self.get_session()
56
58
  return super().request(self, method, url, **kwargs)
usso/user.py CHANGED
@@ -55,8 +55,8 @@ class UserData(BaseModel):
55
55
  acr: str | None = None,
56
56
  amr: list[str] | None = None,
57
57
  signing_level: str | None = None,
58
- **kwargs,
59
- ):
58
+ **kwargs: dict,
59
+ ) -> None:
60
60
  super().__init__(
61
61
  jti=jti,
62
62
  token_type=token_type,
@@ -109,9 +109,9 @@ class UserData(BaseModel):
109
109
  self,
110
110
  *,
111
111
  mode: Literal["json", "python"] | str = "python",
112
- include=None,
113
- exclude=None,
114
- context: Any | None = None,
112
+ include: set[str] | list[str] | None = None,
113
+ exclude: set[str] | list[str] | None = None,
114
+ context: object | None = None,
115
115
  by_alias: bool | None = None,
116
116
  exclude_unset: bool = False,
117
117
  exclude_defaults: bool = False,
@@ -120,7 +120,7 @@ class UserData(BaseModel):
120
120
  warnings: bool | Literal["none", "warn", "error"] = True,
121
121
  fallback: Callable[[Any], Any] | None = None,
122
122
  serialize_as_any: bool = False,
123
- ):
123
+ ) -> dict:
124
124
  return super().model_dump(
125
125
  mode=mode,
126
126
  include=include,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: usso
3
- Version: 0.28.25
3
+ Version: 0.28.27
4
4
  Summary: A plug-and-play client for integrating universal single sign-on (SSO) with Python frameworks, enabling secure and seamless authentication across microservices.
5
5
  Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
6
  Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
@@ -0,0 +1,24 @@
1
+ usso/__init__.py,sha256=ot4Q5ouLGe505DGFAxQP4p4yZLLaBLqbHmCF1OvHG1M,585
2
+ usso/api_key.py,sha256=LtBY86HE27xTk-GCixTL2gyikuIV4XBYWY4OknjUgTk,1262
3
+ usso/authorization.py,sha256=5cROjDZkmUs7eYav32h20WxOexucaF2d4i3_MuKhQ9A,8264
4
+ usso/client.py,sha256=kN6RgYQv08jAiBJ6Z9O89LvWdN_3RXI7O-L89zzLVck,2650
5
+ usso/config.py,sha256=zdiEFqfBhl6eAbfSXuoCZFsfiWBG0tnmozridxSIZ0E,3802
6
+ usso/exceptions.py,sha256=hDxw475zvF55FTEqkfhZfciVXXGiB8pyWgqF2HiUiio,1743
7
+ usso/user.py,sha256=uWL5fkD1QSV6H5qC690iS9MJWX5AvDze6c24l3sXvB0,3846
8
+ usso/integrations/django/__init__.py,sha256=dKpbffHS5ouGtW6ooI2ivzjPmH_1rOBny85htR-KqrY,97
9
+ usso/integrations/django/middleware.py,sha256=LEPb2LkGET47cgGpydcylfBabndX4Hycyzj-xdfREug,3453
10
+ usso/integrations/fastapi/__init__.py,sha256=ohToiqutHu3Okr8naunssDkamj1OdiG4OpPdBW0rt7U,204
11
+ usso/integrations/fastapi/dependency.py,sha256=u7jV3xAo3iW1_a8nu7c2Y2lrq5J8TN3PllYPXu-Oqos,2707
12
+ usso/integrations/fastapi/handler.py,sha256=FcYRWcYsiKNygjAWS1elcy_QQ6neCNsUEE8WyMDtMgA,501
13
+ usso/session/__init__.py,sha256=tE4qWUdSI7iN_pywm47Mg8NKOTBa2nCNwCy3wCZWRmU,124
14
+ usso/session/async_session.py,sha256=iu-bnZHe9_ODSXax_WsclJxtwZ9ClVt7KBl3RysYE-U,4073
15
+ usso/session/base_session.py,sha256=M35m_jBkdGHVPL4R3djuJDdE1aONZnoRvMv-FVrhyaU,2582
16
+ usso/session/session.py,sha256=6f0zz1F_p4hbZgvCAdcMur__U_RaTsZ5R2658e1W-t8,1714
17
+ usso/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ usso/utils/string_utils.py,sha256=7tziAa2Cwa7xhwM_NF4DSY3BHoqVaWgJ21VuV8LvhrY,253
19
+ usso-0.28.27.dist-info/licenses/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
20
+ usso-0.28.27.dist-info/METADATA,sha256=MscismYOkLXNAART0H5HHurMpAt2pf3ePanpJQizyHM,5061
21
+ usso-0.28.27.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
+ usso-0.28.27.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
23
+ usso-0.28.27.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
24
+ usso-0.28.27.dist-info/RECORD,,
@@ -1,24 +0,0 @@
1
- usso/__init__.py,sha256=ot4Q5ouLGe505DGFAxQP4p4yZLLaBLqbHmCF1OvHG1M,585
2
- usso/api_key.py,sha256=tL5aqrmNHs9GKhCQ5NdbSchvR0qCjdZYZdkHwNONwno,1236
3
- usso/authorization.py,sha256=tzGhOy3-avVx0WIRDHADgWR42TxWd4z38X_lDKBdFpo,9022
4
- usso/client.py,sha256=9YTyvro3oz24Pr3i1Dit2R2dpIPsGsuIOol66-8VEyI,2636
5
- usso/config.py,sha256=7GDAh-yHGYppfkhvrwJykhwJp4HH8P_qPAoGcK8_PZQ,3741
6
- usso/exceptions.py,sha256=ggYczQ2eGUH9nBxRYVmOk-6IRSwY8NgEKjMPcE0E5YM,1385
7
- usso/user.py,sha256=YD109KyK0W7LWIH-bXYgtJ53b7Ipb9tLLhwXvwQWyrs,3759
8
- usso/integrations/django/__init__.py,sha256=dKpbffHS5ouGtW6ooI2ivzjPmH_1rOBny85htR-KqrY,97
9
- usso/integrations/django/middleware.py,sha256=AZKYZ4UPNmyxcD3ANgp0y_fdrFvVQdHBqyYxo5XhQUs,3445
10
- usso/integrations/fastapi/__init__.py,sha256=ohToiqutHu3Okr8naunssDkamj1OdiG4OpPdBW0rt7U,204
11
- usso/integrations/fastapi/dependency.py,sha256=Ik1x1tP1QiZ2czr6CYD0gS9Q3P4eUD35gQEANYuESII,2699
12
- usso/integrations/fastapi/handler.py,sha256=MNDoBYdySumFsBgVw-xir3jXXH63KehFXKCh-pNnNZQ,386
13
- usso/session/__init__.py,sha256=tE4qWUdSI7iN_pywm47Mg8NKOTBa2nCNwCy3wCZWRmU,124
14
- usso/session/async_session.py,sha256=eQQh2DXiaHdballRjePa8GSI9GmGsxNDU7vTfwh8mRQ,3971
15
- usso/session/base_session.py,sha256=O3tEltMhlwkEz1GGbjE4iXPwSlLaUW2juUt9RDSLrHI,2559
16
- usso/session/session.py,sha256=briCgDMoF-b59H6Aie_Lmjy4qnPBBSmKnVhAwef34F0,1637
17
- usso/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- usso/utils/string_utils.py,sha256=7tziAa2Cwa7xhwM_NF4DSY3BHoqVaWgJ21VuV8LvhrY,253
19
- usso-0.28.25.dist-info/licenses/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
20
- usso-0.28.25.dist-info/METADATA,sha256=SAquyBkbkvGoVC8QvjrvJrMUkkfXtI6phOUI6qH_xzU,5061
21
- usso-0.28.25.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- usso-0.28.25.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
23
- usso-0.28.25.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
24
- usso-0.28.25.dist-info/RECORD,,
File without changes