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.
Files changed (39) hide show
  1. {shellsmith-0.4.0 → shellsmith-0.6.0}/PKG-INFO +46 -39
  2. {shellsmith-0.4.0 → shellsmith-0.6.0}/README.md +38 -33
  3. {shellsmith-0.4.0 → shellsmith-0.6.0}/pyproject.toml +10 -9
  4. shellsmith-0.6.0/src/shellsmith/auth.py +202 -0
  5. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/clients.py +9 -2
  6. shellsmith-0.4.0/src/shellsmith/cli/__init__.py +0 -0
  7. shellsmith-0.4.0/src/shellsmith/cli/app.py +0 -44
  8. shellsmith-0.4.0/src/shellsmith/cli/args.py +0 -30
  9. shellsmith-0.4.0/src/shellsmith/cli/commands/__init__.py +0 -0
  10. shellsmith-0.4.0/src/shellsmith/cli/commands/decode.py +0 -13
  11. shellsmith-0.4.0/src/shellsmith/cli/commands/encode.py +0 -13
  12. shellsmith-0.4.0/src/shellsmith/cli/commands/groups/__init__.py +0 -0
  13. shellsmith-0.4.0/src/shellsmith/cli/commands/groups/create.py +0 -79
  14. shellsmith-0.4.0/src/shellsmith/cli/commands/groups/delete.py +0 -83
  15. shellsmith-0.4.0/src/shellsmith/cli/commands/groups/get.py +0 -165
  16. shellsmith-0.4.0/src/shellsmith/cli/commands/groups/update.py +0 -111
  17. shellsmith-0.4.0/src/shellsmith/cli/commands/info.py +0 -71
  18. shellsmith-0.4.0/src/shellsmith/cli/commands/nuke.py +0 -66
  19. shellsmith-0.4.0/src/shellsmith/cli/commands/upload.py +0 -32
  20. shellsmith-0.4.0/src/shellsmith/cli/formats.py +0 -13
  21. shellsmith-0.4.0/src/shellsmith/cli/handlers.py +0 -31
  22. shellsmith-0.4.0/src/shellsmith/cli/opts.py +0 -41
  23. shellsmith-0.4.0/src/shellsmith/cli/pretty.py +0 -142
  24. shellsmith-0.4.0/src/shellsmith/cli/resolve.py +0 -30
  25. shellsmith-0.4.0/src/shellsmith/cli/simplify.py +0 -74
  26. shellsmith-0.4.0/src/shellsmith/mcp/__init__.py +0 -5
  27. shellsmith-0.4.0/src/shellsmith/mcp/server.py +0 -613
  28. {shellsmith-0.4.0 → shellsmith-0.6.0}/.gitignore +0 -0
  29. {shellsmith-0.4.0 → shellsmith-0.6.0}/LICENSE +0 -0
  30. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/__init__.py +0 -0
  31. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/__main__.py +0 -0
  32. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/api.py +0 -0
  33. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/config.py +0 -0
  34. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/extract.py +0 -0
  35. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/neo4j.py +0 -0
  36. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/services.py +0 -0
  37. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/types.py +0 -0
  38. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/upload.py +0 -0
  39. {shellsmith-0.4.0 → shellsmith-0.6.0}/src/shellsmith/utils.py +0 -0
@@ -1,7 +1,7 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: shellsmith
3
- Version: 0.4.0
4
- Summary: A Python SDK and CLI for Eclipse BaSyx to manage Asset Administration Shells (AAS)
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,cli,digital-twin,eclipse-basyx,i40,industry40,python,rest-client,typer
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
- Requires-Dist: typer
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/logo-purple-500.png" alt="shellsmith">
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 and CLI for managing [Asset Administration Shells (AAS)](https://industrialdigitaltwin.org/en/content-hub/aasspecifications), Submodels, and Submodel Elements via the [Eclipse BaSyx](https://www.eclipse.org/basyx/) REST API.
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
- ### Features
85
+ ## Installation
86
86
 
87
- - 🐍 **Python SDK** for full CRUD access to Shells, Submodels, and Submodel Elements
88
- - ⚡ **CLI tool** powered by [Typer](https://typer.tiangolo.com/) for fast scripting and automation
89
- - ⚙️ Simple `.env`-based configuration for flexible environment switching
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
- ## 🚀 Installation
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
- ## 🧠 CLI Usage
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
- ## 🐍 Python API Usage
145
+ ## MCP Integration
125
146
 
126
- You can also use `shellsmith` as a Python client library to interact with the BaSyx Environment REST API.
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
- ```python
129
- import shellsmith
149
+ Configure with Claude Desktop or other MCP clients to get AI-powered AAS operations.
130
150
 
131
- # Fetch all AAS Shells
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
- ## 🔧 Configuration
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
- > 💡 The `--host` CLI flag and `host=` Python argument **take precedence** over environment variables and `.env`.
183
+ > ℹ️ The `--host` CLI flag and `host=` Python argument **take precedence** over environment variables and `.env`.
177
184
 
178
- ## 🤝 Contributing
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/logo-purple-500.png" alt="shellsmith">
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 and CLI for managing [Asset Administration Shells (AAS)](https://industrialdigitaltwin.org/en/content-hub/aasspecifications), Submodels, and Submodel Elements via the [Eclipse BaSyx](https://www.eclipse.org/basyx/) REST API.
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
- ### Features
18
+ ## Installation
21
19
 
22
- - 🐍 **Python SDK** for full CRUD access to Shells, Submodels, and Submodel Elements
23
- - ⚡ **CLI tool** powered by [Typer](https://typer.tiangolo.com/) for fast scripting and automation
24
- - ⚙️ Simple `.env`-based configuration for flexible environment switching
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
- ## 🚀 Installation
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
- ## 🧠 CLI Usage
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
- ## 🐍 Python API Usage
78
+ ## MCP Integration
60
79
 
61
- You can also use `shellsmith` as a Python client library to interact with the BaSyx Environment REST API.
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
- ```python
64
- import shellsmith
82
+ Configure with Claude Desktop or other MCP clients to get AI-powered AAS operations.
65
83
 
66
- # Fetch all AAS Shells
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
- ## 🔧 Configuration
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
- > 💡 The `--host` CLI flag and `host=` Python argument **take precedence** over environment variables and `.env`.
116
+ > ℹ️ The `--host` CLI flag and `host=` Python argument **take precedence** over environment variables and `.env`.
112
117
 
113
- ## 🤝 Contributing
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 and CLI for Eclipse BaSyx to manage Asset Administration Shells (AAS)"
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))