usso 0.27.22__py3-none-any.whl → 0.28.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
usso/client/async_api.py DELETED
@@ -1,159 +0,0 @@
1
- import logging
2
-
3
- import httpx
4
- from singleton import Singleton
5
-
6
- from usso.core import UserData, Usso
7
-
8
-
9
- class AsyncUssoAPI(metaclass=Singleton):
10
- def __init__(
11
- self,
12
- url: str = "https://api.usso.io",
13
- api_key: str = None,
14
- refresh_token: str = None,
15
- ):
16
- if url and not url.startswith("http"):
17
- url = f"https://{url}"
18
- url = url.rstrip("/")
19
- self.url = url
20
- assert (
21
- api_key or refresh_token
22
- ), "Either api_key or refresh_token must be provided"
23
- self.api_key = api_key
24
- self.refresh_token = refresh_token
25
- self.access_token = None
26
-
27
- async def _refresh(self, **kwargs):
28
- if not self.refresh_token:
29
- return
30
-
31
- url = f"{self.url}/auth/refresh"
32
- headers = {
33
- "Authorization": f"Bearer {self.refresh_token}",
34
- "Content-Type": "application/json",
35
- }
36
-
37
- async with httpx.AsyncClient() as client:
38
- resp = await client.post(url, headers=headers)
39
- if kwargs.get("raise_exception", True):
40
- resp.raise_for_status()
41
- self.access_token = resp.json().get("access_token")
42
-
43
- def _access_valid(self) -> bool:
44
- if not self.access_token:
45
- return False
46
-
47
- user_data = Usso(
48
- jwks_url=f"{self.url}/website/jwks.json?"
49
- ).user_data_from_token(self.access_token)
50
- return bool(user_data)
51
-
52
- async def _request(
53
- self,
54
- method="get",
55
- endpoint: str = "",
56
- data: dict = None,
57
- **kwargs,
58
- ) -> dict:
59
- url = f"{self.url}/{endpoint}"
60
- headers = {"Content-Type": "application/json"}
61
- if self.api_key:
62
- headers["x-api-key"] = self.api_key
63
- elif self.refresh_token:
64
- if not self.access_token:
65
- await self._refresh()
66
- headers["Authorization"] = f"Bearer {self.access_token}"
67
-
68
- async with httpx.AsyncClient() as client:
69
- try:
70
- resp = await client.request(
71
- method,
72
- url,
73
- headers=headers,
74
- json=data,
75
- )
76
- resp.raise_for_status()
77
- return resp.json()
78
- except httpx.HTTPStatusError as e:
79
- logging.error(f"HTTP error: {e.response.status_code} {e.response.text}")
80
- raise e
81
- except Exception as e:
82
- logging.error(f"Unexpected error: {e}")
83
- raise e
84
-
85
- async def get_users(self, **kwargs) -> list[UserData]:
86
- users_dict = await self._request(endpoint="website/users", **kwargs)
87
- return [UserData(user_id=user.get("uid"), **user) for user in users_dict]
88
-
89
- async def get_user(self, user_id: str, **kwargs) -> UserData:
90
- user_dict = await self._request(endpoint=f"website/users/{user_id}", **kwargs)
91
- return UserData(user_id=user_dict.get("uid"), **user_dict)
92
-
93
- async def get_user_by_credentials(self, credentials: dict, **kwargs) -> UserData:
94
- user_dict = await self._request(
95
- endpoint="website/users/credentials", data=credentials, **kwargs
96
- )
97
- return UserData(user_id=user_dict.get("uid"), **user_dict)
98
-
99
- async def create_user(self, user_data: dict, **kwargs) -> UserData:
100
- user_dict = await self._request(
101
- method="post", endpoint="website/users", data=user_data, **kwargs
102
- )
103
- return UserData(user_id=user_dict.get("uid"), **user_dict)
104
-
105
- async def create_user_credentials(
106
- self, user_id: str, credentials: dict, **kwargs
107
- ) -> UserData:
108
- user_dict = await self._request(
109
- method="post",
110
- endpoint=f"website/users/{user_id}/credentials",
111
- data=credentials,
112
- **kwargs,
113
- )
114
- return UserData(user_id=user_dict.get("uid"), **user_dict)
115
-
116
- async def create_user_by_credentials(
117
- self,
118
- user_data: dict | None = None,
119
- credentials: dict | None = None,
120
- **kwargs,
121
- ) -> UserData:
122
- user_data = user_data or {}
123
- if credentials:
124
- user_data["authenticators"] = [credentials]
125
- user_dict = await self._request(
126
- method="post", endpoint="website/users", data=credentials, **kwargs
127
- )
128
- return UserData(user_id=user_dict.get("uid"), **user_dict)
129
-
130
- async def get_user_payload(self, user_id: str, **kwargs) -> dict:
131
- return await self._request(
132
- endpoint=f"website/users/{user_id}/payload", **kwargs
133
- )
134
-
135
- async def update_user_payload(
136
- self,
137
- user_id: str,
138
- payload: dict,
139
- **kwargs,
140
- ) -> dict:
141
- return await self._request(
142
- method="patch",
143
- endpoint=f"website/users/{user_id}/payload",
144
- data=payload,
145
- **kwargs,
146
- )
147
-
148
- async def set_user_payload(
149
- self,
150
- user_id: str,
151
- payload: dict,
152
- **kwargs,
153
- ) -> dict:
154
- return await self._request(
155
- method="put",
156
- endpoint=f"website/users/{user_id}/payload",
157
- data=payload,
158
- **kwargs,
159
- )
usso/core.py DELETED
@@ -1,160 +0,0 @@
1
- import json
2
- import logging
3
- import os
4
- from datetime import datetime, timedelta
5
- from urllib.parse import urlparse
6
-
7
- import cachetools.func
8
- import httpx
9
- import jwt
10
-
11
- from .exceptions import USSOException
12
- from .schemas import JWTConfig, UserData
13
-
14
- logger = logging.getLogger("usso")
15
-
16
-
17
- def get_authorization_scheme_param(
18
- authorization_header_value: str | None,
19
- ) -> tuple[str, str]:
20
- if not authorization_header_value:
21
- return "", ""
22
- scheme, _, param = authorization_header_value.partition(" ")
23
- return scheme, param
24
-
25
-
26
- def decode_token(key, token: str, algorithms=["RS256"], **kwargs) -> dict:
27
- """Decode a JWT token."""
28
- try:
29
- decoded = jwt.decode(token, key, algorithms=algorithms)
30
- decoded.update({"data": decoded, "token": token})
31
- return UserData(**decoded)
32
- except jwt.ExpiredSignatureError:
33
- _handle_exception("expired_signature", **kwargs)
34
- except jwt.InvalidSignatureError:
35
- _handle_exception("invalid_signature", **kwargs)
36
- except jwt.InvalidAlgorithmError:
37
- _handle_exception("invalid_algorithm", **kwargs)
38
- except jwt.InvalidIssuedAtError:
39
- _handle_exception("invalid_issued_at", **kwargs)
40
- except jwt.InvalidTokenError:
41
- _handle_exception("invalid_token", **kwargs)
42
- except jwt.InvalidKeyError:
43
- _handle_exception("invalid_key", **kwargs)
44
- except Exception as e:
45
- _handle_exception("error", message=str(e), **kwargs)
46
-
47
-
48
- def _handle_exception(error_type: str, **kwargs):
49
- """Handle JWT-related exceptions."""
50
- if kwargs.get("raise_exception", True):
51
- raise USSOException(
52
- status_code=401, error=error_type, message=kwargs.get("message")
53
- )
54
- logger.error(kwargs.get("message") or error_type)
55
-
56
-
57
- def is_expired(token: str, **kwargs) -> bool:
58
- now = datetime.now()
59
- decoded_token: dict = jwt.decode(token, options={"verify_signature": False})
60
- exp = decoded_token.get("exp", (now + timedelta(days=1)).timestamp())
61
- exp = datetime.fromtimestamp(exp)
62
- return exp < now
63
-
64
-
65
- @cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
66
- def get_jwk_keys(jwk_url: str) -> jwt.PyJWKClient:
67
- return jwt.PyJWKClient(jwk_url, headers={"User-Agent": "usso-python"})
68
-
69
-
70
- def decode_token_with_jwk(jwk_url: str, token: str, **kwargs) -> UserData | None:
71
- """Return the user associated with a token value."""
72
- try:
73
- jwk_client = get_jwk_keys(jwk_url)
74
- signing_key = jwk_client.get_signing_key_from_jwt(token)
75
- return decode_token(signing_key.key, token, **kwargs)
76
- except Exception as e:
77
- _handle_exception("error", message=str(e), **kwargs)
78
-
79
-
80
- @cachetools.func.ttl_cache(maxsize=128, ttl=10 * 60)
81
- def fetch_api_key_data(jwk_url: str, api_key: str):
82
- try:
83
- parsed = urlparse(jwk_url)
84
- url = f"{parsed.scheme}://{parsed.netloc}/api_key/verify"
85
- response = httpx.post(url, json={"api_key": api_key})
86
- response.raise_for_status()
87
- return UserData(**response.json())
88
- except Exception as e:
89
- _handle_exception("error", message=str(e))
90
-
91
-
92
-
93
- class Usso:
94
- def __init__(
95
- self,
96
- *,
97
- jwt_config: (
98
- str | dict | JWTConfig | list[str] | list[dict] | list[JWTConfig] | None
99
- ) = None,
100
- jwk_url: str | None = None,
101
- secret: str | None = None,
102
- ):
103
- self.jwt_configs = self._initialize_configs(jwt_config, jwk_url, secret)
104
-
105
- def _initialize_configs(
106
- self,
107
- jwt_config: (
108
- str | dict | JWTConfig | list[str] | list[dict] | list[JWTConfig] | None
109
- ) = None,
110
- jwk_url: str | None = None,
111
- secret: str | None = None,
112
- ):
113
- """Initialize JWT configurations."""
114
- if jwt_config is None:
115
- jwt_config = os.getenv("USSO_JWT_CONFIG")
116
-
117
- if jwt_config is None:
118
- jwk_url = jwk_url or os.getenv("USSO_JWK_URL") or os.getenv("USSO_JWKS_URL")
119
- secret = secret or os.getenv("USSO_SECRET")
120
- if jwk_url:
121
- return [JWTConfig(jwk_url=jwk_url)]
122
- if secret:
123
- return [JWTConfig(secret=secret)]
124
- raise ValueError(
125
- "Provide jwt_config, jwk_url, or secret, or set the appropriate environment variables."
126
- )
127
-
128
- if isinstance(jwt_config, (str, dict, JWTConfig)):
129
- return [self._parse_config(jwt_config)]
130
- if isinstance(jwt_config, list):
131
- return [self._parse_config(config) for config in jwt_config]
132
- raise ValueError("Invalid jwt_config format")
133
-
134
- def _parse_config(self, config):
135
- """Parse a single JWT configuration."""
136
- if isinstance(config, str):
137
- config = json.loads(config)
138
- if isinstance(config, dict):
139
- return JWTConfig(**config)
140
- return config
141
-
142
- def user_data_from_token(self, token: str, **kwargs) -> UserData | None:
143
- """Return the user associated with a token value."""
144
- exp = None
145
- for jwk_config in self.jwt_configs:
146
- try:
147
- user_data = jwk_config.decode(token)
148
- if user_data.token_type.lower() != kwargs.get("token_type", "access"):
149
- _handle_exception("invalid_token_type", **kwargs)
150
- return user_data
151
- except USSOException as e:
152
- exp = e
153
-
154
- if kwargs.get("raise_exception", True):
155
- if exp:
156
- _handle_exception(exp.error, message=str(exp), **kwargs)
157
- _handle_exception("unauthorized", **kwargs)
158
-
159
- def user_data_from_api_key(self, api_key: str):
160
- return fetch_api_key_data(self.jwt_configs[0].jwk_url, api_key)
usso/fastapi/__init__.py DELETED
@@ -1,7 +0,0 @@
1
- from .integration import (
2
- jwt_access_security,
3
- jwt_access_security_None,
4
- jwt_access_security_ws,
5
- )
6
-
7
- __all__ = ["jwt_access_security", "jwt_access_security_ws", "jwt_access_security_None"]
@@ -1,88 +0,0 @@
1
- import logging
2
-
3
- from fastapi import Request, WebSocket
4
- from fastapi.responses import JSONResponse
5
- from starlette.status import HTTP_401_UNAUTHORIZED
6
-
7
- from usso.exceptions import USSOException
8
-
9
- from ..core import UserData, Usso, get_authorization_scheme_param
10
-
11
- logger = logging.getLogger("usso")
12
-
13
-
14
- def get_request_token(request: Request | WebSocket) -> UserData | None:
15
- authorization = request.headers.get("Authorization")
16
- token = None
17
-
18
- if authorization:
19
- scheme, credentials = get_authorization_scheme_param(authorization)
20
- if scheme.lower() == "bearer":
21
- token = credentials
22
-
23
- else:
24
- cookie_token = request.cookies.get("usso_access_token")
25
- if cookie_token:
26
- token = cookie_token
27
-
28
- return token
29
-
30
-
31
- def jwt_access_security_None(request: Request, jwt_config=None) -> UserData | None:
32
- """Return the user associated with a token value."""
33
- api_key = request.headers.get("x-api-key")
34
- if api_key:
35
- return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
36
-
37
- token = get_request_token(request)
38
- if not token:
39
- return None
40
- return Usso(jwt_config=jwt_config).user_data_from_token(
41
- token, raise_exception=False
42
- )
43
-
44
-
45
- def jwt_access_security(request: Request, jwt_config=None) -> UserData | None:
46
- """Return the user associated with a token value."""
47
- api_key = request.headers.get("x-api-key")
48
- if api_key:
49
- return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
50
-
51
- token = get_request_token(request)
52
- if not token:
53
- raise USSOException(
54
- status_code=HTTP_401_UNAUTHORIZED,
55
- error="unauthorized",
56
- message="No token provided",
57
- )
58
-
59
- return Usso(jwt_config=jwt_config).user_data_from_token(token)
60
-
61
-
62
- def jwt_access_security_ws(websocket: WebSocket, jwt_config=None) -> UserData | None:
63
- """Return the user associated with a token value."""
64
- api_key = websocket.headers.get("x-api-key")
65
- if api_key:
66
- return Usso(jwt_config=jwt_config).user_data_from_api_key(api_key)
67
-
68
- token = get_request_token(websocket)
69
- if not token:
70
- raise USSOException(
71
- status_code=HTTP_401_UNAUTHORIZED,
72
- error="unauthorized",
73
- message="No token provided",
74
- )
75
-
76
- return Usso(jwt_config=jwt_config).user_data_from_token(token)
77
-
78
-
79
- async def usso_exception_handler(request: Request, exc: USSOException):
80
- return JSONResponse(
81
- status_code=exc.status_code,
82
- content={"message": exc.message, "error": exc.error},
83
- )
84
-
85
-
86
- EXCEPTION_HANDLERS = {
87
- USSOException: usso_exception_handler,
88
- }
usso/schemas.py DELETED
@@ -1,67 +0,0 @@
1
- import uuid
2
-
3
- import cachetools.func
4
- from pydantic import BaseModel, model_validator
5
-
6
- from . import b64tools
7
-
8
-
9
- class UserData(BaseModel):
10
- user_id: str
11
- workspace_id: str | None = None
12
- workspace_ids: list[str] = []
13
- token_type: str = "access"
14
-
15
- email: str | None = None
16
- phone: str | None = None
17
- username: str | None = None
18
-
19
- authentication_method: str | None = None
20
- is_active: bool = False
21
-
22
- jti: str | None = None
23
- data: dict | None = None
24
-
25
- token: str | None = None
26
-
27
- @property
28
- def uid(self) -> uuid.UUID:
29
- user_id = self.user_id
30
-
31
- if user_id.startswith("u_"):
32
- user_id = user_id[2:]
33
- if 22 <= len(user_id) <= 24:
34
- user_id = b64tools.b64_decode_uuid(user_id)
35
-
36
- return uuid.UUID(user_id)
37
-
38
- @property
39
- def b64id(self) -> uuid.UUID:
40
- return b64tools.b64_encode_uuid_strip(self.uid)
41
-
42
-
43
- class JWTConfig(BaseModel):
44
- """Configuration for JWT processing."""
45
-
46
- jwk_url: str | None = None
47
- secret: str | None = None
48
- algorithm: str = "RS256"
49
- header: dict[str, str] = {"type": "Cookie", "name": "usso_access_token"}
50
-
51
- def __hash__(self):
52
- return hash(self.model_dump_json())
53
-
54
- @model_validator(mode="before")
55
- def validate_config(cls, data: dict):
56
- if not data.get("jwk_url") and not data.get("secret"):
57
- raise ValueError("Either jwk_url or secret must be provided")
58
- return data
59
-
60
- @cachetools.func.ttl_cache(maxsize=128, ttl=600)
61
- def decode(self, token: str):
62
- """Decode a token using the configured method."""
63
- from .core import decode_token, decode_token_with_jwk
64
-
65
- if self.jwk_url:
66
- return decode_token_with_jwk(self.jwk_url, token)
67
- return decode_token(self.secret, token, algorithms=[self.algorithm])
@@ -1,110 +0,0 @@
1
- Metadata-Version: 2.2
2
- Name: usso
3
- Version: 0.27.22
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
- Author-email: Mahdi Kiani <mahdikiany@gmail.com>
6
- Maintainer-email: Mahdi Kiani <mahdikiany@gmail.com>
7
- License: Copyright (c) 2016 The Python Packaging Authority (PyPA)
8
-
9
- Permission is hereby granted, free of charge, to any person obtaining a copy of
10
- this software and associated documentation files (the "Software"), to deal in
11
- the Software without restriction, including without limitation the rights to
12
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
13
- of the Software, and to permit persons to whom the Software is furnished to do
14
- so, subject to the following conditions:
15
-
16
- The above copyright notice and this permission notice shall be included in all
17
- copies or substantial portions of the Software.
18
-
19
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
- SOFTWARE.
26
-
27
- Project-URL: Homepage, https://github.com/ussoio/usso-python
28
- Project-URL: Bug Reports, https://github.com/ussoio/usso-python/issues
29
- Project-URL: Funding, https://github.com/ussoio/usso-python
30
- Project-URL: Say Thanks!, https://saythanks.io/to/mahdikiani
31
- Project-URL: Source, https://github.com/ussoio/usso-python
32
- Keywords: usso,sso,authentication,security,fastapi,django
33
- Classifier: Development Status :: 3 - Alpha
34
- Classifier: Intended Audience :: Developers
35
- Classifier: Topic :: Software Development :: Build Tools
36
- Classifier: License :: OSI Approved :: MIT License
37
- Classifier: Programming Language :: Python :: 3
38
- Classifier: Programming Language :: Python :: 3.10
39
- Classifier: Programming Language :: Python :: 3.11
40
- Classifier: Programming Language :: Python :: 3.12
41
- Classifier: Programming Language :: Python :: 3 :: Only
42
- Requires-Python: >=3.9
43
- Description-Content-Type: text/markdown
44
- License-File: LICENSE.txt
45
- Requires-Dist: pydantic>=2
46
- Requires-Dist: pyjwt[crypto]
47
- Requires-Dist: cachetools
48
- Requires-Dist: singleton_package
49
- Requires-Dist: json-advanced
50
- Requires-Dist: httpx
51
- Provides-Extra: fastapi
52
- Requires-Dist: fastapi>=0.65.0; extra == "fastapi"
53
- Requires-Dist: uvicorn[standard]>=0.13.0; extra == "fastapi"
54
- Provides-Extra: django
55
- Requires-Dist: Django>=3.2; extra == "django"
56
- Provides-Extra: httpx
57
- Requires-Dist: httpx; extra == "httpx"
58
- Provides-Extra: dev
59
- Requires-Dist: check-manifest; extra == "dev"
60
- Provides-Extra: test
61
- Requires-Dist: coverage; extra == "test"
62
- Provides-Extra: all
63
- Requires-Dist: fastapi; extra == "all"
64
- Requires-Dist: uvicorn; extra == "all"
65
- Requires-Dist: django; extra == "all"
66
- Requires-Dist: httpx; extra == "all"
67
- Requires-Dist: dev; extra == "all"
68
- Requires-Dist: test; extra == "all"
69
-
70
- # USSO-Client
71
-
72
- The USSO-Client provides a universal single sign-on (SSO) integration for microservices, making it easy to add secure, scalable authentication across different frameworks. This client simplifies the process of connecting any microservice to the USSO service.
73
-
74
- ## Features
75
-
76
- - **Core SSO Integration**: Use the USSO core client for basic SSO functionality across any Python application.
77
- - **Framework-Specific Modules**:
78
- - **FastAPI Integration**: Specialized support for FastAPI applications, enabling async authentication mechanisms tailored to FastAPI's event loop.
79
- - *Django Integration* (Coming soon): Customizable Django authentication backend that integrates seamlessly with Django's user management and middleware architecture.
80
-
81
- ## Installation
82
-
83
- Install the USSO client using pip:
84
-
85
- ```bash
86
- pip install usso
87
- ```
88
-
89
- To add framework-specific support, use the following commands:
90
-
91
- For FastAPI:
92
-
93
- ```bash
94
- pip install "usso[fastapi]"
95
- ```
96
-
97
- For Django:
98
-
99
- ```bash
100
- pip install "usso[django]"
101
- ```
102
-
103
- ## Quick Start
104
- Follow the quick start guides in the documentation to integrate USSO in your application.
105
-
106
- ## Contributing
107
- Contributions are welcome! See CONTRIBUTING.md for more details on how to get involved.
108
-
109
- ## License
110
- Distributed under the MIT License. See LICENSE for more information.
@@ -1,22 +0,0 @@
1
- usso/__init__.py,sha256=NnOS_S1a-JKTOlGe1nw-kCL3m0y82mA2mDraus7BQ2o,120
2
- usso/b64tools.py,sha256=HGQ0E59vzjrQo2-4jrcY03ebtTaYwTtCZ7KgJaEmxO0,610
3
- usso/core.py,sha256=kZCdHQz-sIbmd9MUd4CVMZACtvP60rbpL1Gszy3tkzQ,5641
4
- usso/exceptions.py,sha256=ogJsjdUK0HoZdQv5uCnzIVoG-bTTMHBqyvB4swAMsiE,653
5
- usso/schemas.py,sha256=aK_UWZvqjZLz5r1yBIZX_nL2yPCNUjxpZ93AsV9mAes,1810
6
- usso/client/__init__.py,sha256=ilGFrugI7bhGXVIcETdbRAye8S7k2mVjkEeziToVzSs,100
7
- usso/client/api.py,sha256=-sluL8BMjcI1z8y8thZElCa63nxo25qlmKh3IzK3M6U,5124
8
- usso/client/async_api.py,sha256=UMN84vmWvLgi-3zsNdhCOjf5zQQQFUJ-wKLztI_rU58,5186
9
- usso/django/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- usso/django/middleware.py,sha256=EEEpHvMQ6QiWw2HY8zQ2Aec0RCATcLWsCKeyiPWJKio,3245
11
- usso/fastapi/__init__.py,sha256=0EcdOzb4f3yu9nILIdGWnlyUz-0VaVX2az1e3f2BusI,201
12
- usso/fastapi/integration.py,sha256=IonxxNj_B9sG2j672rIzE047qo972vk7ch4-eGENp3Q,2638
13
- usso/session/__init__.py,sha256=tE4qWUdSI7iN_pywm47Mg8NKOTBa2nCNwCy3wCZWRmU,124
14
- usso/session/async_session.py,sha256=bOWUeBpE2reDdK9zXrD4ZrZrRWlEOyyafWgPcLg2WLY,3638
15
- usso/session/base_session.py,sha256=1Ad6LbX8IsCPTeEIopnYwybrkPkN5zgGUobv8VHcipA,3430
16
- usso/session/session.py,sha256=T3v3lGvLxs4Yi-zZRzdKHLxEo2rtVewcLk-1V6Y44Jk,2569
17
- usso-0.27.22.dist-info/LICENSE.txt,sha256=ceC9ZJOV9H6CtQDcYmHOS46NA3dHJ_WD4J9blH513pc,1081
18
- usso-0.27.22.dist-info/METADATA,sha256=_di-fqd1GQaRLFMQHK2aVHv6xApGgeOEPlYYjqoI15E,4580
19
- usso-0.27.22.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
20
- usso-0.27.22.dist-info/entry_points.txt,sha256=4Zgpm5ELaAWPf0jPGJFz1_X69H7un8ycT3WdGoJ0Vvk,35
21
- usso-0.27.22.dist-info/top_level.txt,sha256=g9Jf6h1Oyidh0vPiFni7UHInTJjSvu6cUalpLTIvthg,5
22
- usso-0.27.22.dist-info/RECORD,,
File without changes