shellsmith 0.4.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.4.0 → shellsmith-0.6.0}/PKG-INFO +46 -39
- {shellsmith-0.4.0 → shellsmith-0.6.0}/README.md +38 -33
- {shellsmith-0.4.0 → shellsmith-0.6.0}/pyproject.toml +10 -9
- shellsmith-0.6.0/src/shellsmith/auth.py +202 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/clients.py +9 -2
- shellsmith-0.4.0/src/shellsmith/cli/__init__.py +0 -0
- shellsmith-0.4.0/src/shellsmith/cli/app.py +0 -44
- shellsmith-0.4.0/src/shellsmith/cli/args.py +0 -30
- shellsmith-0.4.0/src/shellsmith/cli/commands/__init__.py +0 -0
- shellsmith-0.4.0/src/shellsmith/cli/commands/decode.py +0 -13
- shellsmith-0.4.0/src/shellsmith/cli/commands/encode.py +0 -13
- shellsmith-0.4.0/src/shellsmith/cli/commands/groups/__init__.py +0 -0
- shellsmith-0.4.0/src/shellsmith/cli/commands/groups/create.py +0 -79
- shellsmith-0.4.0/src/shellsmith/cli/commands/groups/delete.py +0 -83
- shellsmith-0.4.0/src/shellsmith/cli/commands/groups/get.py +0 -165
- shellsmith-0.4.0/src/shellsmith/cli/commands/groups/update.py +0 -111
- shellsmith-0.4.0/src/shellsmith/cli/commands/info.py +0 -71
- shellsmith-0.4.0/src/shellsmith/cli/commands/nuke.py +0 -66
- shellsmith-0.4.0/src/shellsmith/cli/commands/upload.py +0 -32
- shellsmith-0.4.0/src/shellsmith/cli/formats.py +0 -13
- shellsmith-0.4.0/src/shellsmith/cli/handlers.py +0 -31
- shellsmith-0.4.0/src/shellsmith/cli/opts.py +0 -41
- shellsmith-0.4.0/src/shellsmith/cli/pretty.py +0 -142
- shellsmith-0.4.0/src/shellsmith/cli/resolve.py +0 -30
- shellsmith-0.4.0/src/shellsmith/cli/simplify.py +0 -74
- shellsmith-0.4.0/src/shellsmith/mcp/__init__.py +0 -5
- shellsmith-0.4.0/src/shellsmith/mcp/server.py +0 -613
- {shellsmith-0.4.0 → shellsmith-0.6.0}/.gitignore +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/LICENSE +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/__init__.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/__main__.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/api.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/config.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/extract.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/neo4j.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/services.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/types.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/upload.py +0 -0
- {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/utils.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: shellsmith
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: A Python SDK
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
7
7
|
Project-URL: Documentation, https://shellsmith.pages.dev
|
|
@@ -28,7 +28,7 @@ License: MIT License
|
|
|
28
28
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
29
|
SOFTWARE.
|
|
30
30
|
License-File: LICENSE
|
|
31
|
-
Keywords: aas,asset-administration-shell,basyx,
|
|
31
|
+
Keywords: aas,asset-administration-shell,basyx,digital-twin,eclipse-basyx,i40,industry40,python,rest-client
|
|
32
32
|
Classifier: Environment :: Console
|
|
33
33
|
Classifier: Intended Audience :: Developers
|
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -41,15 +41,17 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
41
41
|
Classifier: Topic :: Software Development :: Libraries
|
|
42
42
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
43
43
|
Requires-Python: >=3.10
|
|
44
|
-
Requires-Dist: fastmcp
|
|
45
44
|
Requires-Dist: httpx
|
|
46
45
|
Requires-Dist: pydantic-settings
|
|
47
46
|
Requires-Dist: pyyaml
|
|
48
|
-
|
|
47
|
+
Provides-Extra: cli
|
|
48
|
+
Requires-Dist: aas-cli; extra == 'cli'
|
|
49
49
|
Provides-Extra: docs
|
|
50
50
|
Requires-Dist: mkdocs; extra == 'docs'
|
|
51
51
|
Requires-Dist: mkdocs-material; extra == 'docs'
|
|
52
52
|
Requires-Dist: termynal; extra == 'docs'
|
|
53
|
+
Provides-Extra: mcp
|
|
54
|
+
Requires-Dist: aas-mcp; extra == 'mcp'
|
|
53
55
|
Provides-Extra: neo4j
|
|
54
56
|
Requires-Dist: neo4j; extra == 'neo4j'
|
|
55
57
|
Provides-Extra: test
|
|
@@ -64,7 +66,7 @@ Requires-Dist: types-requests; extra == 'test'
|
|
|
64
66
|
Description-Content-Type: text/markdown
|
|
65
67
|
|
|
66
68
|
<div align="center">
|
|
67
|
-
<img src="https://raw.githubusercontent.com/SmartFactory-KL/shellsmith/main/docs/images/
|
|
69
|
+
<img src="https://raw.githubusercontent.com/SmartFactory-KL/shellsmith/main/docs/images/banner-purple-500.png" alt="shellsmith">
|
|
68
70
|
</div>
|
|
69
71
|
|
|
70
72
|
<div align="center">
|
|
@@ -78,26 +80,45 @@ Description-Content-Type: text/markdown
|
|
|
78
80
|
<b>Documentation</b>: <a href="https://shellsmith.pages.dev/" target="_blank">https://shellsmith.pages.dev</a>
|
|
79
81
|
</p>
|
|
80
82
|
|
|
81
|
-
**Shellsmith** is a Python SDK
|
|
82
|
-
|
|
83
|
-
It provides full client-side access to AAS resources with a clean Python interface and a powerful `typer`-based CLI — ideal for scripting, automation, and digital twin integration workflows.
|
|
83
|
+
**Shellsmith** is a Python SDK for managing [Asset Administration Shells (AAS)](https://industrialdigitaltwin.org/en/content-hub/aasspecifications) via the [Eclipse BaSyx](https://www.eclipse.org/basyx/) REST API.
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
## Installation
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
- 🔁 Seamless integration with the [Eclipse BaSyx](https://www.eclipse.org/basyx/) Environment REST API
|
|
87
|
+
```bash
|
|
88
|
+
pip install shellsmith
|
|
89
|
+
```
|
|
91
90
|
|
|
92
|
-
|
|
91
|
+
For CLI and MCP server support, install with optional dependencies:
|
|
93
92
|
|
|
94
93
|
```bash
|
|
95
|
-
pip install shellsmith
|
|
94
|
+
pip install shellsmith[cli,mcp]
|
|
96
95
|
```
|
|
97
96
|
|
|
98
97
|
**Requires**: Python 3.10+
|
|
99
98
|
|
|
100
|
-
##
|
|
99
|
+
## Python API Usage
|
|
100
|
+
|
|
101
|
+
You can also use `shellsmith` as a Python client library to interact with the BaSyx Environment REST API.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import shellsmith
|
|
105
|
+
|
|
106
|
+
# Fetch all AAS Shells
|
|
107
|
+
shells = shellsmith.get_shells()
|
|
108
|
+
|
|
109
|
+
# Fetch a specific Submodel
|
|
110
|
+
submodel = shellsmith.get_submodel("https://example.com/submodels/my-submodel")
|
|
111
|
+
|
|
112
|
+
# Read and update a Submodel Element's value
|
|
113
|
+
value = shellsmith.get_submodel_element_value(submodel["id"], "temperature")
|
|
114
|
+
shellsmith.patch_submodel_element_value(submodel["id"], "temperature", "42.0")
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> ℹ️ `shell_id` and `submodel_id` are automatically base64-encoded unless you pass `encode=False`.
|
|
118
|
+
|
|
119
|
+
➡️ Full Python API reference: [shellsmith.pages.dev/api](https://shellsmith.pages.dev/api)
|
|
120
|
+
|
|
121
|
+
## CLI Usage
|
|
101
122
|
|
|
102
123
|
Shellsmith provides a powerful command-line interface:
|
|
103
124
|
|
|
@@ -121,29 +142,15 @@ aas --help
|
|
|
121
142
|
|
|
122
143
|
➡️ Full CLI reference: [shellsmith.pages.dev/cli](https://shellsmith.pages.dev/cli)
|
|
123
144
|
|
|
124
|
-
##
|
|
145
|
+
## MCP Integration
|
|
125
146
|
|
|
126
|
-
|
|
147
|
+
Shellsmith provides a Model Context Protocol (MCP) server that enables AI assistants to interact with Asset Administration Shells. The server exposes 25+ tools for comprehensive AAS management through a standardized interface.
|
|
127
148
|
|
|
128
|
-
|
|
129
|
-
import shellsmith
|
|
149
|
+
Configure with Claude Desktop or other MCP clients to get AI-powered AAS operations.
|
|
130
150
|
|
|
131
|
-
|
|
132
|
-
shells = shellsmith.get_shells()
|
|
133
|
-
|
|
134
|
-
# Fetch a specific Submodel
|
|
135
|
-
submodel = shellsmith.get_submodel("https://example.com/submodels/my-submodel")
|
|
136
|
-
|
|
137
|
-
# Read and update a Submodel Element's value
|
|
138
|
-
value = shellsmith.get_submodel_element_value(submodel["id"], "temperature")
|
|
139
|
-
shellsmith.patch_submodel_element_value(submodel["id"], "temperature", "42.0")
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
> ℹ️ `shell_id` and `submodel_id` are automatically base64-encoded unless you pass `encode=False`.
|
|
143
|
-
|
|
144
|
-
➡️ Full Python API reference: [shellsmith.pages.dev/api](https://shellsmith.pages.dev/api)
|
|
151
|
+
➡️ Full MCP reference: [shellsmith.pages.dev/mcp](https://shellsmith.pages.dev/mcp)
|
|
145
152
|
|
|
146
|
-
##
|
|
153
|
+
## Configuration
|
|
147
154
|
|
|
148
155
|
The default AAS environment host is:
|
|
149
156
|
|
|
@@ -173,9 +180,9 @@ You can override it in several ways:
|
|
|
173
180
|
shellsmith.get_shells(host="https://your-host:1234")
|
|
174
181
|
```
|
|
175
182
|
|
|
176
|
-
>
|
|
183
|
+
> ℹ️ The `--host` CLI flag and `host=` Python argument **take precedence** over environment variables and `.env`.
|
|
177
184
|
|
|
178
|
-
##
|
|
185
|
+
## Contributing
|
|
179
186
|
|
|
180
187
|
We welcome contributions!
|
|
181
188
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<div align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/SmartFactory-KL/shellsmith/main/docs/images/
|
|
2
|
+
<img src="https://raw.githubusercontent.com/SmartFactory-KL/shellsmith/main/docs/images/banner-purple-500.png" alt="shellsmith">
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
5
|
<div align="center">
|
|
@@ -13,26 +13,45 @@
|
|
|
13
13
|
<b>Documentation</b>: <a href="https://shellsmith.pages.dev/" target="_blank">https://shellsmith.pages.dev</a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
16
|
-
**Shellsmith** is a Python SDK
|
|
17
|
-
|
|
18
|
-
It provides full client-side access to AAS resources with a clean Python interface and a powerful `typer`-based CLI — ideal for scripting, automation, and digital twin integration workflows.
|
|
16
|
+
**Shellsmith** is a Python SDK for managing [Asset Administration Shells (AAS)](https://industrialdigitaltwin.org/en/content-hub/aasspecifications) via the [Eclipse BaSyx](https://www.eclipse.org/basyx/) REST API.
|
|
19
17
|
|
|
20
|
-
|
|
18
|
+
## Installation
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- 🔁 Seamless integration with the [Eclipse BaSyx](https://www.eclipse.org/basyx/) Environment REST API
|
|
20
|
+
```bash
|
|
21
|
+
pip install shellsmith
|
|
22
|
+
```
|
|
26
23
|
|
|
27
|
-
|
|
24
|
+
For CLI and MCP server support, install with optional dependencies:
|
|
28
25
|
|
|
29
26
|
```bash
|
|
30
|
-
pip install shellsmith
|
|
27
|
+
pip install shellsmith[cli,mcp]
|
|
31
28
|
```
|
|
32
29
|
|
|
33
30
|
**Requires**: Python 3.10+
|
|
34
31
|
|
|
35
|
-
##
|
|
32
|
+
## Python API Usage
|
|
33
|
+
|
|
34
|
+
You can also use `shellsmith` as a Python client library to interact with the BaSyx Environment REST API.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
import shellsmith
|
|
38
|
+
|
|
39
|
+
# Fetch all AAS Shells
|
|
40
|
+
shells = shellsmith.get_shells()
|
|
41
|
+
|
|
42
|
+
# Fetch a specific Submodel
|
|
43
|
+
submodel = shellsmith.get_submodel("https://example.com/submodels/my-submodel")
|
|
44
|
+
|
|
45
|
+
# Read and update a Submodel Element's value
|
|
46
|
+
value = shellsmith.get_submodel_element_value(submodel["id"], "temperature")
|
|
47
|
+
shellsmith.patch_submodel_element_value(submodel["id"], "temperature", "42.0")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> ℹ️ `shell_id` and `submodel_id` are automatically base64-encoded unless you pass `encode=False`.
|
|
51
|
+
|
|
52
|
+
➡️ Full Python API reference: [shellsmith.pages.dev/api](https://shellsmith.pages.dev/api)
|
|
53
|
+
|
|
54
|
+
## CLI Usage
|
|
36
55
|
|
|
37
56
|
Shellsmith provides a powerful command-line interface:
|
|
38
57
|
|
|
@@ -56,29 +75,15 @@ aas --help
|
|
|
56
75
|
|
|
57
76
|
➡️ Full CLI reference: [shellsmith.pages.dev/cli](https://shellsmith.pages.dev/cli)
|
|
58
77
|
|
|
59
|
-
##
|
|
78
|
+
## MCP Integration
|
|
60
79
|
|
|
61
|
-
|
|
80
|
+
Shellsmith provides a Model Context Protocol (MCP) server that enables AI assistants to interact with Asset Administration Shells. The server exposes 25+ tools for comprehensive AAS management through a standardized interface.
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
import shellsmith
|
|
82
|
+
Configure with Claude Desktop or other MCP clients to get AI-powered AAS operations.
|
|
65
83
|
|
|
66
|
-
|
|
67
|
-
shells = shellsmith.get_shells()
|
|
68
|
-
|
|
69
|
-
# Fetch a specific Submodel
|
|
70
|
-
submodel = shellsmith.get_submodel("https://example.com/submodels/my-submodel")
|
|
71
|
-
|
|
72
|
-
# Read and update a Submodel Element's value
|
|
73
|
-
value = shellsmith.get_submodel_element_value(submodel["id"], "temperature")
|
|
74
|
-
shellsmith.patch_submodel_element_value(submodel["id"], "temperature", "42.0")
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
> ℹ️ `shell_id` and `submodel_id` are automatically base64-encoded unless you pass `encode=False`.
|
|
78
|
-
|
|
79
|
-
➡️ Full Python API reference: [shellsmith.pages.dev/api](https://shellsmith.pages.dev/api)
|
|
84
|
+
➡️ Full MCP reference: [shellsmith.pages.dev/mcp](https://shellsmith.pages.dev/mcp)
|
|
80
85
|
|
|
81
|
-
##
|
|
86
|
+
## Configuration
|
|
82
87
|
|
|
83
88
|
The default AAS environment host is:
|
|
84
89
|
|
|
@@ -108,9 +113,9 @@ You can override it in several ways:
|
|
|
108
113
|
shellsmith.get_shells(host="https://your-host:1234")
|
|
109
114
|
```
|
|
110
115
|
|
|
111
|
-
>
|
|
116
|
+
> ℹ️ The `--host` CLI flag and `host=` Python argument **take precedence** over environment variables and `.env`.
|
|
112
117
|
|
|
113
|
-
##
|
|
118
|
+
## Contributing
|
|
114
119
|
|
|
115
120
|
We welcome contributions!
|
|
116
121
|
|
|
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|
|
6
6
|
name = "shellsmith"
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
authors = [{ name = "Peter Stein", email = "peter.stein@dfki.de" }]
|
|
9
|
-
description = "A Python SDK
|
|
9
|
+
description = "A Python SDK for Eclipse BaSyx to manage Asset Administration Shells (AAS)"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
11
11
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
12
12
|
requires-python = ">=3.10"
|
|
@@ -31,17 +31,13 @@ keywords = [
|
|
|
31
31
|
"industry40",
|
|
32
32
|
"i40",
|
|
33
33
|
"digital-twin",
|
|
34
|
-
"cli",
|
|
35
|
-
"typer",
|
|
36
34
|
"rest-client",
|
|
37
35
|
"python",
|
|
38
36
|
]
|
|
39
37
|
dependencies = [
|
|
40
38
|
"httpx",
|
|
41
39
|
"pydantic-settings",
|
|
42
|
-
"fastmcp",
|
|
43
40
|
"pyyaml",
|
|
44
|
-
"typer",
|
|
45
41
|
]
|
|
46
42
|
|
|
47
43
|
[project.optional-dependencies]
|
|
@@ -55,19 +51,21 @@ test = [
|
|
|
55
51
|
"types-pyyaml",
|
|
56
52
|
"types-requests"
|
|
57
53
|
]
|
|
54
|
+
cli = [
|
|
55
|
+
"aas-cli",
|
|
56
|
+
]
|
|
58
57
|
docs = [
|
|
59
58
|
"mkdocs",
|
|
60
59
|
"mkdocs-material",
|
|
61
60
|
"termynal",
|
|
62
61
|
]
|
|
62
|
+
mcp = [
|
|
63
|
+
"aas-mcp",
|
|
64
|
+
]
|
|
63
65
|
neo4j = [
|
|
64
66
|
"neo4j",
|
|
65
67
|
]
|
|
66
68
|
|
|
67
|
-
[project.scripts]
|
|
68
|
-
aas = "shellsmith.cli.app:main"
|
|
69
|
-
shellsmith-mcp = "shellsmith.mcp.server:cli_main"
|
|
70
|
-
|
|
71
69
|
[project.urls]
|
|
72
70
|
Homepage = "https://github.com/SmartFactory-KL/shellsmith"
|
|
73
71
|
Issues = "https://github.com/SmartFactory-KL/shellsmith/issues"
|
|
@@ -109,3 +107,6 @@ ignore = [
|
|
|
109
107
|
|
|
110
108
|
[tool.ruff.lint.pydocstyle]
|
|
111
109
|
convention = "google"
|
|
110
|
+
|
|
111
|
+
[tool.ruff.lint.pylint]
|
|
112
|
+
max-args = 6
|
|
@@ -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
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
"""Main CLI entry point for shellsmith with Typer."""
|
|
2
|
-
|
|
3
|
-
import httpx
|
|
4
|
-
import typer
|
|
5
|
-
|
|
6
|
-
from .commands.decode import app as decode_app
|
|
7
|
-
from .commands.encode import app as encode_app
|
|
8
|
-
from .commands.groups.create import app as create_app
|
|
9
|
-
from .commands.groups.delete import app as delete_app
|
|
10
|
-
from .commands.groups.get import app as get_app
|
|
11
|
-
from .commands.groups.update import app as update_app
|
|
12
|
-
from .commands.info import app as info_app
|
|
13
|
-
from .commands.nuke import app as nuke_app
|
|
14
|
-
from .commands.upload import app as upload_app
|
|
15
|
-
|
|
16
|
-
app = typer.Typer(
|
|
17
|
-
help="shellsmith - AAS Toolkit command-line interface.",
|
|
18
|
-
no_args_is_help=True,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
app.add_typer(upload_app)
|
|
22
|
-
app.add_typer(info_app)
|
|
23
|
-
app.add_typer(nuke_app)
|
|
24
|
-
app.add_typer(encode_app)
|
|
25
|
-
app.add_typer(decode_app)
|
|
26
|
-
app.add_typer(get_app)
|
|
27
|
-
app.add_typer(delete_app)
|
|
28
|
-
app.add_typer(update_app)
|
|
29
|
-
app.add_typer(create_app)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def main() -> None:
|
|
33
|
-
"""Main entry point for the CLI."""
|
|
34
|
-
try:
|
|
35
|
-
app()
|
|
36
|
-
except httpx.ConnectError as e:
|
|
37
|
-
typer.secho(f"😩 {e}", fg=typer.colors.RED)
|
|
38
|
-
except Exception as e:
|
|
39
|
-
typer.secho(f"💥 Unexpected error: {e}", fg=typer.colors.RED)
|
|
40
|
-
raise
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if __name__ == "__main__":
|
|
44
|
-
main()
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
"""Reusable CLI arguments in commands."""
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
|
|
5
|
-
SHELL_ID: typer.Argument = typer.Argument(
|
|
6
|
-
...,
|
|
7
|
-
help="The unique identifier of the Shell.",
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
SUBMODEL_ID: typer.Argument = typer.Argument(
|
|
11
|
-
...,
|
|
12
|
-
help="The unique identifier of the Submodel.",
|
|
13
|
-
)
|
|
14
|
-
|
|
15
|
-
ID_SHORT_PATH: typer.Argument = typer.Argument(
|
|
16
|
-
...,
|
|
17
|
-
help="The idShort path for the Submodel Element.",
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
OPTIONAL_ID_SHORT_PATH: typer.Argument = typer.Argument(
|
|
21
|
-
None,
|
|
22
|
-
help="Optional idShort path for a nested Submodel Element.",
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
AAS_PATH: typer.Argument = typer.Argument(
|
|
26
|
-
...,
|
|
27
|
-
help="The path to the AAS file or folder to upload. Accepts: .json, .xml, .aasx",
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
VALUE: typer.Argument = typer.Argument(..., help="The new value as string.")
|
|
File without changes
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""CLI command for base64-decoding unique identifiers."""
|
|
2
|
-
|
|
3
|
-
import typer
|
|
4
|
-
|
|
5
|
-
from shellsmith.utils import base64_decode
|
|
6
|
-
|
|
7
|
-
app = typer.Typer()
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@app.command()
|
|
11
|
-
def decode(value: str) -> None:
|
|
12
|
-
"""Decodes a Base64 value."""
|
|
13
|
-
print(base64_decode(value))
|