shellsmith 0.5.0__tar.gz → 0.6.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.
- {shellsmith-0.5.0 → shellsmith-0.6.0}/PKG-INFO +2 -2
- {shellsmith-0.5.0 → shellsmith-0.6.0}/pyproject.toml +3 -0
- shellsmith-0.6.0/src/shellsmith/auth.py +202 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/clients.py +9 -2
- {shellsmith-0.5.0 → shellsmith-0.6.0}/.gitignore +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/LICENSE +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/README.md +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/__init__.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/__main__.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/api.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/config.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/extract.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/neo4j.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/services.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/types.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/upload.py +0 -0
- {shellsmith-0.5.0 → shellsmith-0.6.0}/src/shellsmith/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: shellsmith
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: A Python SDK for Eclipse BaSyx to manage Asset Administration Shells (AAS)
|
|
5
5
|
Project-URL: Homepage, https://github.com/SmartFactory-KL/shellsmith
|
|
6
6
|
Project-URL: Issues, https://github.com/SmartFactory-KL/shellsmith/issues
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Sync and async auth."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import typing
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from httpx import Request, Response
|
|
9
|
+
from typing_extensions import override
|
|
10
|
+
|
|
11
|
+
from shellsmith.config import config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class UserAuthentication:
|
|
16
|
+
"""Authentication data for user-based authentication.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
username: Username used for authentication.
|
|
20
|
+
password: Password used for authentication.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
username: str
|
|
24
|
+
password: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ClientAuthentication:
|
|
29
|
+
"""Authentication data for client-based authentication.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
client_id: The unique identifier of the client used for authentication.
|
|
33
|
+
client_secret: The secret associated with the client used for authentication.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
client_id: str
|
|
37
|
+
client_secret: str
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TokenProvider:
|
|
41
|
+
"""Base class for token providers.
|
|
42
|
+
|
|
43
|
+
To implement a custom token provider scheme, subclass `TokenProvider`
|
|
44
|
+
and provide data.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
token_url: str,
|
|
50
|
+
data: dict[str, str],
|
|
51
|
+
timeout: float = config.timeout,
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Initialize a token provider.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
token_url: URL of the token endpoint.
|
|
57
|
+
data: Data required to request an access token,
|
|
58
|
+
including the grant type and authentication credentials.
|
|
59
|
+
timeout: Request timeout in seconds.
|
|
60
|
+
"""
|
|
61
|
+
self._token_url = token_url
|
|
62
|
+
self._timeout = timeout
|
|
63
|
+
|
|
64
|
+
self._token = None
|
|
65
|
+
self._expires_at = 0
|
|
66
|
+
|
|
67
|
+
self._data = data
|
|
68
|
+
|
|
69
|
+
def _token_valid(self) -> bool:
|
|
70
|
+
"""Return whether the current token exists and has not expired."""
|
|
71
|
+
return self._token and time.time() < self._expires_at
|
|
72
|
+
|
|
73
|
+
def _save_token(self, data: dict[str, typing.Any]) -> None:
|
|
74
|
+
"""Save the access token and calculate its expiration time.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
data: Token response containing ``access_token`` and optionally
|
|
78
|
+
``expires_in`` in seconds.
|
|
79
|
+
"""
|
|
80
|
+
self._token = data["access_token"]
|
|
81
|
+
self._expires_at = time.time() + data.get("expires_in", 3600)
|
|
82
|
+
|
|
83
|
+
def sync_get_token(self) -> str:
|
|
84
|
+
"""Return a valid access token synchronously, refreshing it when necessary."""
|
|
85
|
+
if self._token_valid():
|
|
86
|
+
return self._token
|
|
87
|
+
|
|
88
|
+
with httpx.Client() as client:
|
|
89
|
+
response = client.post(
|
|
90
|
+
self._token_url, timeout=self._timeout, data=self._data
|
|
91
|
+
)
|
|
92
|
+
response.raise_for_status()
|
|
93
|
+
self._save_token(response.json())
|
|
94
|
+
|
|
95
|
+
return self._token
|
|
96
|
+
|
|
97
|
+
async def async_get_token(self) -> str:
|
|
98
|
+
"""Return a valid access token asynchronously, refreshing it when necessary."""
|
|
99
|
+
if self._token_valid():
|
|
100
|
+
return self._token
|
|
101
|
+
|
|
102
|
+
async with httpx.AsyncClient() as client:
|
|
103
|
+
r = await client.post(
|
|
104
|
+
self._token_url, timeout=self._timeout, data=self._data
|
|
105
|
+
)
|
|
106
|
+
r.raise_for_status()
|
|
107
|
+
payload = r.json()
|
|
108
|
+
|
|
109
|
+
self._save_token(payload)
|
|
110
|
+
return self._token
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class PasswordTokenProvider(TokenProvider):
|
|
114
|
+
"""Token provider using password grant type (see https://www.rfc-editor.org/info/rfc6749/#section-4.3)."""
|
|
115
|
+
|
|
116
|
+
def __init__(
|
|
117
|
+
self,
|
|
118
|
+
token_url: str,
|
|
119
|
+
user_authentication: UserAuthentication,
|
|
120
|
+
client_authentication: ClientAuthentication | None = None,
|
|
121
|
+
timeout: float = config.timeout,
|
|
122
|
+
) -> None:
|
|
123
|
+
"""Initialize a password token provider.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
token_url: URL of the token endpoint.
|
|
127
|
+
user_authentication: Username and password.
|
|
128
|
+
client_authentication: Optional client identifier and client secret.
|
|
129
|
+
timeout: Request timeout in seconds.
|
|
130
|
+
"""
|
|
131
|
+
_data = {
|
|
132
|
+
"username": user_authentication.username,
|
|
133
|
+
"password": user_authentication.password,
|
|
134
|
+
"grant_type": "password",
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if client_authentication:
|
|
138
|
+
_data.update(
|
|
139
|
+
{
|
|
140
|
+
"client_id": client_authentication.client_id,
|
|
141
|
+
"client_secret": client_authentication.client_secret,
|
|
142
|
+
}
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
super().__init__(token_url=token_url, data=_data, timeout=timeout)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ClientCredentialsTokenProvider(TokenProvider):
|
|
149
|
+
"""Token provider using client credentials grant type (see https://www.rfc-editor.org/info/rfc6749/#section-2.3)."""
|
|
150
|
+
|
|
151
|
+
def __init__(
|
|
152
|
+
self,
|
|
153
|
+
token_url: str,
|
|
154
|
+
client_authentication: ClientAuthentication,
|
|
155
|
+
timeout: float = config.timeout,
|
|
156
|
+
) -> None:
|
|
157
|
+
"""Initialize a client credentials token provider.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
token_url: URL of the token endpoint.
|
|
161
|
+
client_authentication: Client identifier and client secret.
|
|
162
|
+
timeout: Request timeout in seconds.
|
|
163
|
+
"""
|
|
164
|
+
super().__init__(
|
|
165
|
+
token_url=token_url,
|
|
166
|
+
data={
|
|
167
|
+
"client_id": client_authentication.client_id,
|
|
168
|
+
"client_secret": client_authentication.client_secret,
|
|
169
|
+
"grant_type": "client_credentials",
|
|
170
|
+
},
|
|
171
|
+
timeout=timeout,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Auth(httpx.Auth):
|
|
176
|
+
"""Authentication handler that adds an access token to httpx.request."""
|
|
177
|
+
|
|
178
|
+
def __init__(self, token_provider: "TokenProvider") -> None:
|
|
179
|
+
"""Initialize the authentication handler.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
token_provider: Provider used to obtain an access token.
|
|
183
|
+
"""
|
|
184
|
+
self.token_provider = token_provider
|
|
185
|
+
|
|
186
|
+
@override
|
|
187
|
+
def sync_auth_flow(
|
|
188
|
+
self, request: Request
|
|
189
|
+
) -> typing.Generator[Request, Response, None]:
|
|
190
|
+
"""Synchronously fetch a token and add it to the request header."""
|
|
191
|
+
token = self.token_provider.sync_get_token()
|
|
192
|
+
request.headers["Authorization"] = f"Bearer {token}"
|
|
193
|
+
yield request
|
|
194
|
+
|
|
195
|
+
@override
|
|
196
|
+
async def async_auth_flow(
|
|
197
|
+
self, request: Request
|
|
198
|
+
) -> typing.Generator[Request, Response, None]:
|
|
199
|
+
"""Asynchronously fetch a token and add it to the request header."""
|
|
200
|
+
token = await self.token_provider.async_get_token()
|
|
201
|
+
request.headers["Authorization"] = f"Bearer {token}"
|
|
202
|
+
yield request
|
|
@@ -9,6 +9,7 @@ from urllib.parse import quote
|
|
|
9
9
|
import httpx
|
|
10
10
|
from httpx import Response
|
|
11
11
|
|
|
12
|
+
from shellsmith.auth import Auth
|
|
12
13
|
from shellsmith.config import config
|
|
13
14
|
from shellsmith.types import JSON
|
|
14
15
|
from shellsmith.utils import base64_encoded
|
|
@@ -21,20 +22,23 @@ class AsyncClient:
|
|
|
21
22
|
self,
|
|
22
23
|
host: str = config.host,
|
|
23
24
|
timeout: float = config.timeout,
|
|
25
|
+
auth: Auth | None = None,
|
|
24
26
|
) -> None:
|
|
25
27
|
"""Initialize async client.
|
|
26
28
|
|
|
27
29
|
Args:
|
|
28
30
|
host: Base URL of the AAS server. Defaults to configured host.
|
|
29
31
|
timeout: Request timeout in seconds.
|
|
32
|
+
auth: Authentication configuration for requests.
|
|
30
33
|
"""
|
|
31
34
|
self.host = host
|
|
32
35
|
self.timeout = timeout
|
|
33
36
|
self._client: httpx.AsyncClient | None = None
|
|
37
|
+
self._auth = auth
|
|
34
38
|
|
|
35
39
|
async def __aenter__(self) -> "AsyncClient":
|
|
36
40
|
"""Enter async context manager."""
|
|
37
|
-
self._client = httpx.AsyncClient(timeout=self.timeout)
|
|
41
|
+
self._client = httpx.AsyncClient(timeout=self.timeout, auth=self._auth)
|
|
38
42
|
return self
|
|
39
43
|
|
|
40
44
|
async def __aexit__(
|
|
@@ -705,20 +709,23 @@ class Client:
|
|
|
705
709
|
self,
|
|
706
710
|
host: str = config.host,
|
|
707
711
|
timeout: float = config.timeout,
|
|
712
|
+
auth: Auth | None = None,
|
|
708
713
|
) -> None:
|
|
709
714
|
"""Initialize sync client.
|
|
710
715
|
|
|
711
716
|
Args:
|
|
712
717
|
host: Base URL of the AAS server. Defaults to configured host.
|
|
713
718
|
timeout: Request timeout in seconds.
|
|
719
|
+
auth: Authentication configuration for requests.
|
|
714
720
|
"""
|
|
715
721
|
self.host = host
|
|
716
722
|
self.timeout = timeout
|
|
717
723
|
self._client: httpx.Client | None = None
|
|
724
|
+
self._auth = auth
|
|
718
725
|
|
|
719
726
|
def __enter__(self) -> "Client":
|
|
720
727
|
"""Enter context manager."""
|
|
721
|
-
self._client = httpx.Client(timeout=self.timeout)
|
|
728
|
+
self._client = httpx.Client(timeout=self.timeout, auth=self._auth)
|
|
722
729
|
return self
|
|
723
730
|
|
|
724
731
|
def __exit__(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|