aas-http-client 0.4.1__tar.gz → 0.4.3__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.

Potentially problematic release.


This version of aas-http-client might be problematic. Click here for more details.

Files changed (23) hide show
  1. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/PKG-INFO +1 -1
  2. aas_http_client-0.4.3/aas_http_client/classes/auth_classes.py +21 -0
  3. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/client.py +222 -26
  4. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/demo/demo_process.py +23 -10
  5. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/wrapper/sdk_wrapper.py +16 -2
  6. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client.egg-info/PKG-INFO +1 -1
  7. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client.egg-info/SOURCES.txt +1 -0
  8. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/pyproject.toml +1 -1
  9. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/tests/test_client.py +55 -1
  10. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/LICENSE +0 -0
  11. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/README.md +0 -0
  12. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/__init__.py +0 -0
  13. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/core/encoder.py +0 -0
  14. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/core/version_check.py +0 -0
  15. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/demo/logging_handler.py +0 -0
  16. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/utilities/__init__.py +0 -0
  17. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/utilities/model_builder.py +0 -0
  18. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client/utilities/sdk_tools.py +0 -0
  19. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client.egg-info/dependency_links.txt +0 -0
  20. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client.egg-info/requires.txt +0 -0
  21. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/aas_http_client.egg-info/top_level.txt +0 -0
  22. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/setup.cfg +0 -0
  23. {aas_http_client-0.4.1 → aas_http_client-0.4.3}/tests/test_wrapper.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aas-http-client
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Generic python HTTP client for communication with various types of AAS servers
5
5
  Author-email: Daniel Klein <daniel.klein@em.ag>
6
6
  License: # :em engineering methods AG Software License
@@ -0,0 +1,21 @@
1
+ from pydantic import BaseModel, PrivateAttr, ValidationError
2
+
3
+
4
+ class BasicAuthConfig(BaseModel):
5
+ username: str
6
+
7
+
8
+ class ServiceProviderAuthConfig(BaseModel):
9
+ token_url: str
10
+ client_id: str
11
+ grant_type: str = "client_credentials"
12
+ header_name: str = "Authorization"
13
+ _client_secret: str = PrivateAttr(default=None)
14
+
15
+ def set_client_secret(self, client_secret: str) -> None:
16
+ self._client_secret = client_secret
17
+
18
+ def get_client_secret(self) -> str:
19
+ if self._client_secret is None:
20
+ raise ValueError("Client secret has not been set.")
21
+ return self._client_secret
@@ -4,10 +4,8 @@ import json
4
4
  import logging
5
5
  import time
6
6
  from pathlib import Path
7
- from typing import Any
8
7
 
9
8
  import basyx.aas.adapter.json
10
- import basyx.aas.adapter.json.json_serialization as js
11
9
  import requests
12
10
  from basyx.aas.model import Reference, Submodel
13
11
  from pydantic import BaseModel, PrivateAttr, ValidationError
@@ -15,6 +13,7 @@ from requests import Session
15
13
  from requests.auth import HTTPBasicAuth
16
14
  from requests.models import Response
17
15
 
16
+ from aas_http_client.classes.auth_classes import BasicAuthConfig, ServiceProviderAuthConfig
18
17
  from aas_http_client.core.encoder import decode_base_64
19
18
 
20
19
  logger = logging.getLogger(__name__)
@@ -74,16 +73,22 @@ class AasHttpClient(BaseModel):
74
73
  """Represents a AasHttpClient to communicate with a REST API."""
75
74
 
76
75
  base_url: str = "http://javaaasserver:5060/"
77
- username: str | None = None
76
+ basic_auth: BasicAuthConfig | None = None
77
+ service_provider_auth: ServiceProviderAuthConfig | None = None
78
78
  https_proxy: str | None = None
79
79
  http_proxy: str | None = None
80
80
  time_out: int = 200
81
81
  connection_time_out: int = 100
82
82
  ssl_verify: bool = True
83
83
  trust_env: bool = True
84
+ auth_service_provider: str | None = None
84
85
  _session: Session = PrivateAttr(default=None)
85
86
 
86
- def initialize(self, password: str):
87
+ def initialize(
88
+ self,
89
+ basic_auth_password: str,
90
+ service_provider_auth_client_secret: str,
91
+ ):
87
92
  """Initialize the AasHttpClient with the given URL, username and password.
88
93
 
89
94
  :param password: password
@@ -92,10 +97,17 @@ class AasHttpClient(BaseModel):
92
97
  self.base_url = self.base_url[:-1]
93
98
 
94
99
  self._session = requests.Session()
95
- self._session.auth = HTTPBasicAuth(self.username, password)
100
+
101
+ self._session.auth = HTTPBasicAuth("", "")
102
+ if self.basic_auth:
103
+ self._session.auth = HTTPBasicAuth(self.basic_auth.username, basic_auth_password)
104
+
96
105
  self._session.verify = self.ssl_verify
97
106
  self._session.trust_env = self.trust_env
98
107
 
108
+ if self.service_provider_auth:
109
+ self.service_provider_auth.set_client_secret(service_provider_auth_client_secret)
110
+
99
111
  if self.https_proxy:
100
112
  self._session.proxies.update({"https": self.https_proxy})
101
113
  if self.http_proxy:
@@ -108,6 +120,9 @@ class AasHttpClient(BaseModel):
108
120
  """
109
121
  url = f"{self.base_url}/shells"
110
122
 
123
+ if self.service_provider_auth:
124
+ self._set_token_by_client_credentials()
125
+
111
126
  try:
112
127
  response = self._session.get(url, headers=HEADERS, timeout=10)
113
128
  logger.debug(f"Call REST API url '{response.url}'")
@@ -123,6 +138,29 @@ class AasHttpClient(BaseModel):
123
138
  content = response.content.decode("utf-8")
124
139
  return json.loads(content)
125
140
 
141
+ def _set_token_by_client_credentials(self) -> dict | None:
142
+ if self.service_provider_auth is None:
143
+ logger.error("Service provider authentication is not configured.")
144
+ return None
145
+
146
+ if self.service_provider_auth.grant_type == "password":
147
+ token = get_token_by_password(
148
+ self.service_provider_auth.token_url,
149
+ self.service_provider_auth.client_id,
150
+ self.service_provider_auth.get_client_secret(),
151
+ self.time_out,
152
+ )
153
+ else:
154
+ token = get_token_by_basic_auth(
155
+ self.service_provider_auth.token_url,
156
+ self.service_provider_auth.client_id,
157
+ self.service_provider_auth.get_client_secret(),
158
+ self.time_out,
159
+ )
160
+
161
+ if token:
162
+ self._session.headers.update({self.service_provider_auth.auth_header_name: f"Bearer {token}"})
163
+
126
164
  # region shells
127
165
 
128
166
  def post_asset_administration_shell(self, aas_data: dict) -> dict | None:
@@ -134,6 +172,9 @@ class AasHttpClient(BaseModel):
134
172
  url = f"{self.base_url}/shells"
135
173
  logger.debug(f"Call REST API url '{url}'")
136
174
 
175
+ if self.service_provider_auth:
176
+ self._set_token_by_client_credentials()
177
+
137
178
  try:
138
179
  response = self._session.post(url, headers=HEADERS, json=aas_data, timeout=self.time_out)
139
180
  logger.debug(f"Call REST API url '{response.url}'")
@@ -159,6 +200,9 @@ class AasHttpClient(BaseModel):
159
200
  decoded_identifier: str = decode_base_64(identifier)
160
201
  url = f"{self.base_url}/shells/{decoded_identifier}"
161
202
 
203
+ if self.service_provider_auth:
204
+ self._set_token_by_client_credentials()
205
+
162
206
  try:
163
207
  response = self._session.put(url, headers=HEADERS, json=aas_data, timeout=self.time_out)
164
208
  logger.debug(f"Call REST API url '{response.url}'")
@@ -184,6 +228,9 @@ class AasHttpClient(BaseModel):
184
228
  decoded_submodel_id: str = decode_base_64(submodel_id)
185
229
  url = f"{self.base_url}/shells/{decoded_aas_id}/submodels/{decoded_submodel_id}"
186
230
 
231
+ if self.service_provider_auth:
232
+ self._set_token_by_client_credentials()
233
+
187
234
  try:
188
235
  response = self._session.put(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
189
236
  logger.debug(f"Call REST API url '{response.url}'")
@@ -205,6 +252,9 @@ class AasHttpClient(BaseModel):
205
252
  """
206
253
  url = f"{self.base_url}/shells"
207
254
 
255
+ if self.service_provider_auth:
256
+ self._set_token_by_client_credentials()
257
+
208
258
  try:
209
259
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
210
260
  logger.debug(f"Call REST API url '{response.url}'")
@@ -229,6 +279,9 @@ class AasHttpClient(BaseModel):
229
279
  decoded_aas_id: str = decode_base_64(aas_id)
230
280
  url = f"{self.base_url}/shells/{decoded_aas_id}"
231
281
 
282
+ if self.service_provider_auth:
283
+ self._set_token_by_client_credentials()
284
+
232
285
  try:
233
286
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
234
287
  logger.debug(f"Call REST API url '{response.url}'")
@@ -253,6 +306,9 @@ class AasHttpClient(BaseModel):
253
306
  decoded_aas_id: str = decode_base_64(aas_id)
254
307
  url = f"{self.base_url}/shells/{decoded_aas_id}/$reference"
255
308
 
309
+ if self.service_provider_auth:
310
+ self._set_token_by_client_credentials()
311
+
256
312
  try:
257
313
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
258
314
  logger.debug(f"Call REST API url '{response.url}'")
@@ -279,7 +335,9 @@ class AasHttpClient(BaseModel):
279
335
  decoded_submodel_id: str = decode_base_64(submodel_id)
280
336
 
281
337
  url = f"{self.base_url}/shells/{decoded_aas_id}/submodels/{decoded_submodel_id}"
282
- # /shells/{aasIdentifier}/submodels/{submodelIdentifier}
338
+
339
+ if self.service_provider_auth:
340
+ self._set_token_by_client_credentials()
283
341
 
284
342
  try:
285
343
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
@@ -305,6 +363,9 @@ class AasHttpClient(BaseModel):
305
363
  decoded_aas_id: str = decode_base_64(aas_id)
306
364
  url = f"{self.base_url}/shells/{decoded_aas_id}"
307
365
 
366
+ if self.service_provider_auth:
367
+ self._set_token_by_client_credentials()
368
+
308
369
  try:
309
370
  response = self._session.delete(url, headers=HEADERS, timeout=self.time_out)
310
371
  logger.debug(f"Call REST API url '{response.url}'")
@@ -331,6 +392,9 @@ class AasHttpClient(BaseModel):
331
392
  """
332
393
  url = f"{self.base_url}/submodels"
333
394
 
395
+ if self.service_provider_auth:
396
+ self._set_token_by_client_credentials()
397
+
334
398
  try:
335
399
  response = self._session.post(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
336
400
  logger.debug(f"Call REST API url '{response.url}'")
@@ -356,6 +420,9 @@ class AasHttpClient(BaseModel):
356
420
  decoded_identifier: str = decode_base_64(identifier)
357
421
  url = f"{self.base_url}/submodels/{decoded_identifier}"
358
422
 
423
+ if self.service_provider_auth:
424
+ self._set_token_by_client_credentials()
425
+
359
426
  try:
360
427
  response = self._session.put(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
361
428
  logger.debug(f"Call REST API url '{response.url}'")
@@ -377,6 +444,9 @@ class AasHttpClient(BaseModel):
377
444
  """
378
445
  url = f"{self.base_url}/submodels"
379
446
 
447
+ if self.service_provider_auth:
448
+ self._set_token_by_client_credentials()
449
+
380
450
  try:
381
451
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
382
452
  logger.debug(f"Call REST API url '{response.url}'")
@@ -401,6 +471,9 @@ class AasHttpClient(BaseModel):
401
471
  decoded_submodel_id: str = decode_base_64(submodel_id)
402
472
  url = f"{self.base_url}/submodels/{decoded_submodel_id}"
403
473
 
474
+ if self.service_provider_auth:
475
+ self._set_token_by_client_credentials()
476
+
404
477
  try:
405
478
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
406
479
  logger.debug(f"Call REST API url '{response.url}'")
@@ -425,6 +498,9 @@ class AasHttpClient(BaseModel):
425
498
  decoded_submodel_id: str = decode_base_64(submodel_id)
426
499
  url = f"{self.base_url}/submodels/{decoded_submodel_id}"
427
500
 
501
+ if self.service_provider_auth:
502
+ self._set_token_by_client_credentials()
503
+
428
504
  try:
429
505
  response = self._session.patch(url, headers=HEADERS, json=submodel_data, timeout=self.time_out)
430
506
  logger.debug(f"Call REST API url '{response.url}'")
@@ -448,6 +524,9 @@ class AasHttpClient(BaseModel):
448
524
  decoded_submodel_id: str = decode_base_64(submodel_id)
449
525
  url = f"{self.base_url}/submodels/{decoded_submodel_id}"
450
526
 
527
+ if self.service_provider_auth:
528
+ self._set_token_by_client_credentials()
529
+
451
530
  try:
452
531
  response = self._session.delete(url, headers=HEADERS, timeout=self.time_out)
453
532
  logger.debug(f"Call REST API url '{response.url}'")
@@ -471,6 +550,9 @@ class AasHttpClient(BaseModel):
471
550
  decoded_submodel_id: str = decode_base_64(submodel_id)
472
551
  url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements"
473
552
 
553
+ if self.service_provider_auth:
554
+ self._set_token_by_client_credentials()
555
+
474
556
  try:
475
557
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
476
558
  logger.debug(f"Call REST API url '{response.url}'")
@@ -495,6 +577,38 @@ class AasHttpClient(BaseModel):
495
577
  decoded_submodel_id: str = decode_base_64(submodel_id)
496
578
  url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements"
497
579
 
580
+ if self.service_provider_auth:
581
+ self._set_token_by_client_credentials()
582
+
583
+ try:
584
+ response = self._session.post(url, headers=HEADERS, json=submodel_element_data, timeout=self.time_out)
585
+ logger.debug(f"Call REST API url '{response.url}'")
586
+
587
+ if response.status_code != STATUS_CODE_201:
588
+ log_response_errors(response)
589
+ return None
590
+
591
+ except requests.exceptions.RequestException as e:
592
+ logger.error(f"Error call REST API: {e}")
593
+ return None
594
+
595
+ content = response.content.decode("utf-8")
596
+ return json.loads(content)
597
+
598
+ def post_submodel_element_by_path_submodel_repo(self, submodel_id: str, submodel_element_path: str, submodel_element_data: dict) -> dict | None:
599
+ """Creates a new submodel element at a specified path within submodel elements hierarchy.
600
+
601
+ :param submodel_id: Encoded ID of the Submodel to create elements for
602
+ :param submodel_element_path: Path within the Submodel elements hierarchy
603
+ :param submodel_element_data: Data for the new Submodel element
604
+ :return: Submodel element data or None if an error occurred
605
+ """
606
+ decoded_submodel_id: str = decode_base_64(submodel_id)
607
+ url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements/{submodel_element_path}"
608
+
609
+ if self.service_provider_auth:
610
+ self._set_token_by_client_credentials()
611
+
498
612
  try:
499
613
  response = self._session.post(url, headers=HEADERS, json=submodel_element_data, timeout=self.time_out)
500
614
  logger.debug(f"Call REST API url '{response.url}'")
@@ -521,6 +635,9 @@ class AasHttpClient(BaseModel):
521
635
 
522
636
  url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements/{submodel_element_path}"
523
637
 
638
+ if self.service_provider_auth:
639
+ self._set_token_by_client_credentials()
640
+
524
641
  try:
525
642
  response = self._session.get(url, headers=HEADERS, timeout=self.time_out)
526
643
  logger.debug(f"Call REST API url '{response.url}'")
@@ -547,6 +664,9 @@ class AasHttpClient(BaseModel):
547
664
 
548
665
  url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements/{submodel_element_path}"
549
666
 
667
+ if self.service_provider_auth:
668
+ self._set_token_by_client_credentials()
669
+
550
670
  try:
551
671
  response = self._session.delete(url, headers=HEADERS, timeout=self.time_out)
552
672
  logger.debug(f"Call REST API url '{response.url}'")
@@ -573,6 +693,9 @@ class AasHttpClient(BaseModel):
573
693
 
574
694
  url = f"{self.base_url}/submodels/{decoded_submodel_id}/submodel-elements/{submodel_element_path}/$value"
575
695
 
696
+ if self.service_provider_auth:
697
+ self._set_token_by_client_credentials()
698
+
576
699
  try:
577
700
  response = self._session.patch(url, headers=HEADERS, json=value, timeout=self.time_out)
578
701
  logger.debug(f"Call REST API url '{response.url}'")
@@ -593,10 +716,68 @@ class AasHttpClient(BaseModel):
593
716
  # region client
594
717
 
595
718
 
719
+ def get_token_by_basic_auth(endpoint: str, username: str, password: str, timeout=200) -> dict | None:
720
+ """Get token from a specific authentication service provider by basic authentication.
721
+
722
+ :param endpoint: Get token endpoint for the authentication service provider
723
+ :param username: Username for the authentication service provider
724
+ :param password: Password for the authentication service provider
725
+ :param timeout: Timeout for the API calls, defaults to 200
726
+ :return: Access token or None if an error occurred
727
+ """
728
+ data = {"grant_type": "client_credentials"}
729
+
730
+ auth = HTTPBasicAuth(username, password)
731
+
732
+ try:
733
+ response = requests.post(endpoint, auth=auth, data=data, timeout=timeout)
734
+ logger.info(f"Request URL: {response.url}")
735
+ response.raise_for_status()
736
+ return response.json().get("access_token")
737
+ except requests.RequestException as e:
738
+ logger.error(f"Error getting token: {e}")
739
+ return None
740
+
741
+
742
+ def get_token_by_password(endpoint: str, username: str, password: str, timeout=200) -> dict | None:
743
+ """Get token from a specific authentication service provider by username and password.
744
+
745
+ :param endpoint: Get token endpoint for the authentication service provider
746
+ :param username: Username for the authentication service provider
747
+ :param password: Password for the authentication service provider
748
+ :param timeout: Timeout for the API calls, defaults to 200
749
+ :return: Access token or None if an error occurred
750
+ """
751
+ data = {"grant_type": "password", "username": username, "password": password}
752
+
753
+ return _get_token(endpoint, data, timeout)
754
+
755
+
756
+ def _get_token(endpoint: str, data: dict[str, str], timeout: int = 200) -> dict | None:
757
+ """Get token from a specific authentication service provider.
758
+
759
+ :param endpoint: Get token endpoint for the authentication service provider
760
+ :param data: Data for the authentication service provider
761
+ :param timeout: Timeout for the API calls, defaults to 200
762
+ :return: Access token or None if an error occurred
763
+ """
764
+ try:
765
+ response = requests.post(endpoint, json=data, timeout=timeout)
766
+ logger.info(f"Request URL: {response.url}")
767
+ response.raise_for_status()
768
+ return response.json().get("access_token")
769
+ except requests.RequestException as e:
770
+ logger.error(f"Error getting token: {e}")
771
+ return None
772
+
773
+
596
774
  def create_client_by_url(
597
775
  base_url: str,
598
- username: str = "",
599
- password: str = "",
776
+ basic_auth_username: str = "",
777
+ basic_auth_password: str = "",
778
+ service_provider_auth_client_id: str = "",
779
+ service_provider_auth_client_secret: str = "",
780
+ service_provider_auth_token_url: str = "",
600
781
  http_proxy: str = "",
601
782
  https_proxy: str = "",
602
783
  time_out: int = 200,
@@ -620,34 +801,45 @@ def create_client_by_url(
620
801
  logger.info(f"Create AAS server http client from URL '{base_url}'.")
621
802
  config_dict: dict[str, str] = {}
622
803
  config_dict["base_url"] = base_url
623
- config_dict["username"] = username
624
804
  config_dict["http_proxy"] = http_proxy
625
805
  config_dict["https_proxy"] = https_proxy
626
806
  config_dict["time_out"] = time_out
627
807
  config_dict["connection_time_out"] = connection_time_out
628
808
  config_dict["ssl_verify"] = ssl_verify
629
809
  config_dict["trust_env"] = trust_env
630
- return create_client_by_dict(config_dict, password)
810
+ config_dict["basic_auth"] = None
811
+ config_dict["service_provider_auth"] = None
812
+
813
+ if basic_auth_password and basic_auth_username:
814
+ config_dict["basic_auth"] = {"username": basic_auth_username}
815
+
816
+ if service_provider_auth_client_id and service_provider_auth_client_secret and service_provider_auth_token_url:
817
+ config_dict["service_provider_auth"] = {
818
+ "client_id": service_provider_auth_client_id,
819
+ "token_url": service_provider_auth_token_url,
820
+ }
631
821
 
822
+ return create_client_by_dict(config_dict, basic_auth_password, service_provider_auth_client_secret)
632
823
 
633
- def create_client_by_dict(configuration: dict, password: str = "") -> AasHttpClient | None:
824
+
825
+ def create_client_by_dict(configuration: dict, basic_auth_password: str = "", service_provider_auth_client_secret: str = "") -> AasHttpClient | None:
634
826
  """Create a HTTP client for a AAS server connection from the given configuration.
635
827
 
636
828
  :param configuration: Dictionary containing the BaSyx server connection settings.
637
- :param password: Password for the AAS server, defaults to ""_
829
+ :param basic_auth_password: Password for the AAS server basic auth, defaults to ""
638
830
  :return: An instance of Http client initialized with the provided parameters.
639
831
  """
640
832
  logger.info("Create AAS server http client from dictionary.")
641
833
  config_string = json.dumps(configuration, indent=4)
642
834
 
643
- return _create_client(config_string, password)
835
+ return _create_client(config_string, basic_auth_password, service_provider_auth_client_secret)
644
836
 
645
837
 
646
- def create_client_by_config(config_file: Path, password: str = "") -> AasHttpClient | None:
838
+ def create_client_by_config(config_file: Path, basic_auth_password: str = "", service_provider_auth_client_secret: str = "") -> AasHttpClient | None:
647
839
  """Create a HTTP client for a AAS server connection from a given configuration file.
648
840
 
649
841
  :param config_file: Path to the configuration file containing the AAS server connection settings.
650
- :param password: password for the BaSyx server interface client, defaults to ""_
842
+ :param basic_auth_password: password for the BaSyx server basic auth, defaults to ""
651
843
  :return: An instance of Http client initialized with the provided parameters.
652
844
  """
653
845
  config_file = config_file.resolve()
@@ -659,24 +851,28 @@ def create_client_by_config(config_file: Path, password: str = "") -> AasHttpCli
659
851
  config_string = config_file.read_text(encoding="utf-8")
660
852
  logger.debug(f"Configuration file '{config_file}' found.")
661
853
 
662
- return _create_client(config_string, password)
854
+ return _create_client(config_string, basic_auth_password, service_provider_auth_client_secret)
663
855
 
664
856
 
665
- def _create_client(config_string: str, password: str) -> AasHttpClient | None:
857
+ def _create_client(config_string: str, basic_auth_password: str, service_provider_auth_client_secret: str) -> AasHttpClient | None:
666
858
  try:
667
859
  client = AasHttpClient.model_validate_json(config_string)
668
860
  except ValidationError as ve:
669
861
  raise ValidationError(f"Invalid BaSyx server configuration file: {ve}") from ve
670
862
 
671
- logger.info(
672
- f"Using server configuration: '{client.base_url}' | "
673
- f"timeout: '{client.time_out}' | "
674
- f"username: '{client.username}' | "
675
- f"https_proxy: '{client.https_proxy}' | "
676
- f"http_proxy: '{client.http_proxy}' | "
677
- f"connection_timeout: '{client.connection_time_out}'."
678
- )
679
- client.initialize(password)
863
+ logger.info("Using server configuration:")
864
+ logger.info(f"base_url: '{client.base_url}'")
865
+ logger.info(f"timeout: '{client.time_out}'")
866
+
867
+ if client.basic_auth:
868
+ logger.info(f"basic_auth: '{client.basic_auth.username}'")
869
+ if client.service_provider_auth:
870
+ logger.info(f"service_provider_auth: '{client.service_provider_auth.token_url}': {client.service_provider_auth.client_id}'")
871
+
872
+ logger.info(f"https_proxy: '{client.https_proxy}'")
873
+ logger.info(f"http_proxy: '{client.http_proxy}'")
874
+ logger.info(f"connection_timeout: '{client.connection_time_out}'.")
875
+ client.initialize(basic_auth_password, service_provider_auth_client_secret)
680
876
 
681
877
  # test the connection to the REST API
682
878
  connected = _connect_to_api(client)
@@ -5,9 +5,9 @@ from pathlib import Path
5
5
 
6
6
  from basyx.aas import model
7
7
 
8
- from aas_http_client.client import AasHttpClient, create_client_by_config
8
+ from aas_http_client import client as aas_client
9
9
  from aas_http_client.utilities import model_builder, sdk_tools
10
- from aas_http_client.wrapper.sdk_wrapper import SdkWrapper, create_wrapper_by_config
10
+ from aas_http_client.wrapper import sdk_wrapper
11
11
 
12
12
  logger = logging.getLogger(__name__)
13
13
 
@@ -21,20 +21,33 @@ def start() -> None:
21
21
 
22
22
  # create a submodel
23
23
  sm_short_id: str = model_builder.create_unique_short_id("poc_sm")
24
- submodel = model_builder.create_base_submodel(sm_short_id)
24
+ submodel = model_builder.create_base_submodel(sm_short_id, sm_short_id)
25
25
  # add submodel element to submodel
26
26
  # submodel.submodel_element.add(sme)
27
27
 
28
28
  # create an AAS
29
29
  aas_short_id: str = model_builder.create_unique_short_id("poc_aas")
30
- aas = model_builder.create_base_ass(aas_short_id)
30
+ aas = model_builder.create_base_ass(aas_short_id, aas_short_id)
31
31
 
32
32
  # add submodel to AAS
33
33
  sdk_tools.add_submodel_to_aas(aas, submodel)
34
34
 
35
- wrapper = _create_sdk_wrapper(Path("./aas_http_client/demo/python_server_config.yml"))
36
- # dotnet_sdk_wrapper = _create_sdk_wrapper(Path("./aas_http_client/demo/dotnet_server_config.yml"))
35
+ client = aas_client.create_client_by_url(
36
+ "http://javaaasserver:8075/",
37
+ service_provider_auth_client_id="fluid40",
38
+ service_provider_auth_client_secret="LdFB4jRrMMkgcVWgFkOVdDVDXtQ5os8w",
39
+ service_provider_auth_token_url="https://aurora-fluid40.iqstruct-engineering.de/auth/realms/BaSyx/protocol/openid-connect/token",
40
+ )
41
+
42
+ client = aas_client.create_client_by_config(Path("./aas_http_client/demo/java_server_config.yml"))
37
43
 
44
+ # tmp = get_token_by_basic_auth(
45
+ # "https://aurora-fluid40.iqstruct-engineering.de/auth/realms/BaSyx/protocol/openid-connect/token",
46
+ # "fluid40",
47
+ # "LdFB4jRrMMkgcVWgFkOVdDVDXtQ5os8w",
48
+ # )
49
+
50
+ wrapper = _create_sdk_wrapper(Path("./aas_http_client/demo/python_server_config.yml"))
38
51
  for existing_shell in wrapper.get_all_asset_administration_shells():
39
52
  logger.warning(f"Delete shell '{existing_shell.id}'")
40
53
  wrapper.delete_asset_administration_shell_by_id(existing_shell.id)
@@ -64,7 +77,7 @@ def start() -> None:
64
77
  wrapper.delete_submodel_by_id(existing_submodel.id)
65
78
 
66
79
 
67
- def _create_client(config: Path) -> AasHttpClient:
80
+ def _create_client(config: Path) -> aas_client.AasHttpClient:
68
81
  """Create a HTTP client from a given configuration file.
69
82
 
70
83
  :param config: Given configuration file
@@ -72,7 +85,7 @@ def _create_client(config: Path) -> AasHttpClient:
72
85
  """
73
86
  try:
74
87
  file = config
75
- client = create_client_by_config(file, password="")
88
+ client = aas_client.create_client_by_config(file, password="")
76
89
 
77
90
  except Exception as e:
78
91
  logger.error(f"Failed to create client for {file}: {e}")
@@ -80,7 +93,7 @@ def _create_client(config: Path) -> AasHttpClient:
80
93
  return client
81
94
 
82
95
 
83
- def _create_sdk_wrapper(config: Path) -> SdkWrapper:
96
+ def _create_sdk_wrapper(config: Path) -> sdk_wrapper.SdkWrapper:
84
97
  """Create a SDK wrapper from a given configuration file.
85
98
 
86
99
  :param config: Given configuration file
@@ -88,7 +101,7 @@ def _create_sdk_wrapper(config: Path) -> SdkWrapper:
88
101
  """
89
102
  try:
90
103
  file = config
91
- client = create_wrapper_by_config(file, password="")
104
+ client = sdk_wrapper.create_wrapper_by_config(file, password="")
92
105
 
93
106
  except Exception as e:
94
107
  logger.error(f"Failed to create client for {file}: {e}")
@@ -25,7 +25,7 @@ class SdkWrapper:
25
25
  :param config_string: Configuration string for the BaSyx server connection.
26
26
  :param password: Password for the BaSyx server interface client, defaults to "".
27
27
  """
28
- client = _create_client(config_string, password)
28
+ client = _create_client(config_string, password, "")
29
29
 
30
30
  if not client:
31
31
  raise ValueError("Failed to create AAS HTTP client with the provided configuration.")
@@ -257,7 +257,7 @@ class SdkWrapper:
257
257
  return submodel_elements
258
258
 
259
259
  def post_submodel_element_submodel_repo(self, submodel_id: str, submodel_element: model.SubmodelElement) -> model.SubmodelElement | None:
260
- """Creates a new submodel element. !!!Serialization to model.SubmodelElements currently not possible.
260
+ """Creates a new submodel element.
261
261
 
262
262
  :param submodel_id: Encoded ID of the submodel to create elements for
263
263
  :param submodel_element: Submodel element to create
@@ -267,6 +267,20 @@ class SdkWrapper:
267
267
  content: dict = self._client.post_submodel_element_submodel_repo(submodel_id, sme_data)
268
268
  return _to_object(content)
269
269
 
270
+ def post_submodel_element_by_path_submodel_repo(
271
+ self, submodel_id: str, submodel_element_path: str, submodel_element: model.SubmodelElement
272
+ ) -> model.SubmodelElement | None:
273
+ """Creates a new submodel element at a specified path within submodel elements hierarchy.
274
+
275
+ :param submodel_id: Encoded ID of the submodel to create elements for
276
+ :param submodel_element_path: Path within the Submodel elements hierarchy
277
+ :param submodel_element: The new Submodel element
278
+ :return: Submodel element object or None if an error occurred
279
+ """
280
+ sme_data = _to_dict(submodel_element)
281
+ content: dict = self._client.post_submodel_element_by_path_submodel_repo(submodel_id, submodel_element_path, sme_data)
282
+ return _to_object(content)
283
+
270
284
  def get_submodel_element_by_path_submodel_repo(self, submodel_id: str, submodel_element_path: str) -> model.SubmodelElement | None:
271
285
  """Returns a specific submodel element from the Submodel at a specified path.
272
286
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aas-http-client
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: Generic python HTTP client for communication with various types of AAS servers
5
5
  Author-email: Daniel Klein <daniel.klein@em.ag>
6
6
  License: # :em engineering methods AG Software License
@@ -8,6 +8,7 @@ aas_http_client.egg-info/SOURCES.txt
8
8
  aas_http_client.egg-info/dependency_links.txt
9
9
  aas_http_client.egg-info/requires.txt
10
10
  aas_http_client.egg-info/top_level.txt
11
+ aas_http_client/classes/auth_classes.py
11
12
  aas_http_client/core/encoder.py
12
13
  aas_http_client/core/version_check.py
13
14
  aas_http_client/demo/demo_process.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aas-http-client"
7
- version = "0.4.1"
7
+ version = "0.4.3"
8
8
  description = "Generic python HTTP client for communication with various types of AAS servers"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -29,7 +29,7 @@ def client(request) -> AasHttpClient:
29
29
  if not file.exists():
30
30
  raise FileNotFoundError(f"Configuration file {file} does not exist.")
31
31
 
32
- client = create_client_by_config(file, password="")
32
+ client = create_client_by_config(file)
33
33
  except Exception as e:
34
34
  raise RuntimeError("Unable to connect to server.")
35
35
 
@@ -555,6 +555,60 @@ def test_018d_patch_submodel_element_by_path_value_only_submodel_repo(client: Aa
555
555
  assert get_result.get("description", {})[0].get("text", "") == shared_sme_float.description.get("en", "")
556
556
  assert get_result.get("displayName", {})[0].get("text", "") == shared_sme_float.display_name.get("en", "")
557
557
 
558
+ def test_019a_post_submodel_element_by_path_submodel_repo(client: AasHttpClient, shared_sm: model.Submodel):
559
+ submodel_element_list = model.SubmodelElementList(id_short="sme_list_1", type_value_list_element=model.Property, value_type_list_element=model.datatypes.String)
560
+ submodel_element_list_dict = sdk_tools.convert_to_dict(submodel_element_list)
561
+ first_result = client.post_submodel_element_submodel_repo(shared_sm.id, submodel_element_list_dict)
562
+
563
+ assert first_result is not None
564
+
565
+ property = model_builder.create_base_submodel_element_property("sme_property_in_list", model.datatypes.String, "Value in List")
566
+ property_dict = sdk_tools.convert_to_dict(property)
567
+ del property_dict["idShort"]
568
+
569
+ result = client.post_submodel_element_by_path_submodel_repo(shared_sm.id, submodel_element_list.id_short, property_dict)
570
+
571
+ assert result is not None
572
+ assert "idShort" not in result # idShort was deleted
573
+
574
+ submodel = client.get_submodel_by_id(shared_sm.id)
575
+
576
+ assert submodel is not None
577
+ elements = submodel.get("submodelElements", [])
578
+ assert len(elements) == 5 # 4 previous properties + 1 list
579
+ assert elements[4].get("idShort", "") == submodel_element_list.id_short
580
+ list_elements = elements[4].get("value", [])
581
+ assert len(list_elements) == 1
582
+ assert list_elements[0].get("idShort", "") == ""
583
+ assert list_elements[0].get("value", "") == property.value
584
+
585
+
586
+ def test_019b_post_submodel_element_by_path_submodel_repo(client: AasHttpClient, shared_sm: model.Submodel):
587
+ submodel_element_collection = model.SubmodelElementCollection(id_short="sme_collection_1")
588
+ submodel_element_collection_dict = sdk_tools.convert_to_dict(submodel_element_collection)
589
+ first_result = client.post_submodel_element_submodel_repo(shared_sm.id, submodel_element_collection_dict)
590
+
591
+ assert first_result is not None
592
+
593
+ property = model_builder.create_base_submodel_element_property("sme_property_in_collection", model.datatypes.String, "Value in List")
594
+ property_dict = sdk_tools.convert_to_dict(property)
595
+
596
+ result = client.post_submodel_element_by_path_submodel_repo(shared_sm.id, submodel_element_collection.id_short, property_dict)
597
+
598
+ assert result is not None
599
+ assert result["idShort"] == property.id_short
600
+
601
+ submodel = client.get_submodel_by_id(shared_sm.id)
602
+
603
+ assert submodel is not None
604
+ elements = submodel.get("submodelElements", [])
605
+ assert len(elements) == 6
606
+ assert elements[5].get("idShort", "") == submodel_element_collection.id_short
607
+ list_elements = elements[5].get("value", [])
608
+ assert len(list_elements) == 1
609
+ assert list_elements[0].get("idShort", "") == property.id_short
610
+ assert list_elements[0].get("value", "") == property.value
611
+
558
612
  def test_098_delete_asset_administration_shell_by_id(client: AasHttpClient, shared_aas: model.AssetAdministrationShell):
559
613
  result = client.delete_asset_administration_shell_by_id(shared_aas.id)
560
614
 
File without changes