shapeshifter-uftp 1.1.1.dev2__tar.gz → 1.1.2__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 (54) hide show
  1. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/PKG-INFO +1 -1
  2. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/README.rst +4 -0
  3. shapeshifter-uftp-1.1.2/pyproject.toml +18 -0
  4. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/setup.py +40 -40
  5. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/service/base_service.py +0 -4
  6. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/cro_dso.py +8 -7
  7. shapeshifter-uftp-1.1.2/shapeshifter_uftp/uftp/parsing.py +8 -0
  8. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/validations.py +32 -29
  9. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp.egg-info/PKG-INFO +1 -1
  10. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp.egg-info/SOURCES.txt +2 -0
  11. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp.egg-info/requires.txt +4 -4
  12. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_communications.py +54 -57
  13. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/setup.cfg +0 -0
  14. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/__init__.py +0 -0
  15. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/cli.py +0 -0
  16. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/__init__.py +0 -0
  17. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/agr_cro_client.py +0 -0
  18. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/agr_dso_client.py +0 -0
  19. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/base_client.py +0 -0
  20. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/cro_agr_client.py +0 -0
  21. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/cro_dso_client.py +0 -0
  22. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/dso_agr_client.py +0 -0
  23. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/client/dso_cro_client.py +0 -0
  24. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/exceptions.py +0 -0
  25. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/logging.py +0 -0
  26. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/service/__init__.py +0 -0
  27. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/service/agr_service.py +0 -0
  28. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/service/cro_service.py +0 -0
  29. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/service/dso_service.py +0 -0
  30. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/transport.py +0 -0
  31. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/__init__.py +0 -0
  32. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/agr_cro.py +0 -0
  33. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/agr_dso.py +0 -0
  34. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/common.py +0 -0
  35. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/defaults.py +0 -0
  36. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp/uftp/metering.py +0 -0
  37. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp.egg-info/dependency_links.txt +0 -0
  38. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp.egg-info/entry_points.txt +0 -0
  39. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/shapeshifter_uftp.egg-info/top_level.txt +0 -0
  40. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_cli.py +0 -0
  41. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_client_errors.py +0 -0
  42. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_client_with_workers.py +0 -0
  43. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_clients_from_service.py +0 -0
  44. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_default_responses.py +0 -0
  45. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_message_destination.py +0 -0
  46. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_presence_of_client_methods.py +0 -0
  47. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_presence_of_service_methods.py +0 -0
  48. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_roundtrip_serialization.py +0 -0
  49. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_seal_and_unseal.py +0 -0
  50. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_service_errors.py +0 -0
  51. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_snake_case.py +0 -0
  52. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_ttl_cache.py +0 -0
  53. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_validations.py +0 -0
  54. {shapeshifter-uftp-1.1.1.dev2 → shapeshifter-uftp-1.1.2}/test/test_xml_schema_compliance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shapeshifter-uftp
3
- Version: 1.1.1.dev2
3
+ Version: 1.1.2
4
4
  Summary: Allows connections between DSO, AGR and CRO using the Shapeshifter (UFTP) protocol.
5
5
  Requires-Python: >=3.10
6
6
  Provides-Extra: dev
@@ -41,6 +41,10 @@ Version History
41
41
  +-------------+-------------------+----------------------------------+
42
42
  | Version | Release Date | Release Notes |
43
43
  +=============+===================+==================================+
44
+ | 1.1.2 | 2024-03-12 | Pinned dependencies after a |
45
+ | | | breaking update to fastapi-xml |
46
+ | | | was released. |
47
+ +-------------+-------------------+----------------------------------+
44
48
  | 1.1.0 | 2023-08-30 | Use the published 3.0.0 spec |
45
49
  | | | for the XSD validation and |
46
50
  | | | objects. |
@@ -0,0 +1,18 @@
1
+ [tool.pytest.ini_options]
2
+ addopts = "-v --cov --cov-report html"
3
+
4
+ [tool.coverage.run]
5
+ include = [
6
+ "shapeshifter_uftp/**/*.py",
7
+ ]
8
+ omit = [
9
+ "application/asgi.py",
10
+ ]
11
+
12
+ [tool.coverage.report]
13
+ skip_empty = true
14
+
15
+ [tool.coverage.html]
16
+ directory = "test_coverage"
17
+
18
+ [tool.pylint.DESIGN]
@@ -1,40 +1,40 @@
1
- from setuptools import setup
2
-
3
- setup(
4
- name="shapeshifter-uftp",
5
- version="1.1.1.dev2",
6
- python_requires=">=3.10",
7
- description="Allows connections between DSO, AGR and CRO using the Shapeshifter (UFTP) protocol.",
8
- packages=[
9
- "shapeshifter_uftp",
10
- "shapeshifter_uftp.uftp",
11
- "shapeshifter_uftp.client",
12
- "shapeshifter_uftp.service",
13
- ],
14
- install_requires=[
15
- "xsdata[lxml]",
16
- "pynacl",
17
- "dnspython",
18
- "fastapi<0.100",
19
- "fastapi-xml",
20
- "requests",
21
- "uvicorn",
22
- "termcolor",
23
- ],
24
- extras_require={
25
- "dev": [
26
- "xmlschema",
27
- "pytest",
28
- "pytest-cov",
29
- "pylint",
30
- "sphinx",
31
- "sphinx-rtd-theme",
32
- ]
33
- },
34
- entry_points={
35
- "console_scripts": [
36
- "shapeshifter-keypair = shapeshifter_uftp.cli:generate_signing_keypair",
37
- "shapeshifter-lookup = shapeshifter_uftp.cli:perform_lookup",
38
- ]
39
- },
40
- )
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name="shapeshifter-uftp",
5
+ version="1.1.2",
6
+ python_requires=">=3.10",
7
+ description="Allows connections between DSO, AGR and CRO using the Shapeshifter (UFTP) protocol.",
8
+ packages=[
9
+ "shapeshifter_uftp",
10
+ "shapeshifter_uftp.uftp",
11
+ "shapeshifter_uftp.client",
12
+ "shapeshifter_uftp.service",
13
+ ],
14
+ install_requires=[
15
+ "xsdata[lxml]==24.3.1",
16
+ "pynacl==1.5.0",
17
+ "dnspython==2.6.1",
18
+ "fastapi<0.100",
19
+ "fastapi-xml==1.0.0b1",
20
+ "requests",
21
+ "uvicorn",
22
+ "termcolor",
23
+ ],
24
+ extras_require={
25
+ "dev": [
26
+ "xmlschema",
27
+ "pytest",
28
+ "pytest-cov",
29
+ "pylint",
30
+ "sphinx",
31
+ "sphinx-rtd-theme",
32
+ ]
33
+ },
34
+ entry_points={
35
+ "console_scripts": [
36
+ "shapeshifter-keypair = shapeshifter_uftp.cli:generate_signing_keypair",
37
+ "shapeshifter-lookup = shapeshifter_uftp.cli:perform_lookup",
38
+ ]
39
+ },
40
+ )
@@ -4,7 +4,6 @@ from uuid import uuid4
4
4
  from time import sleep
5
5
  from threading import Thread
6
6
  import re
7
- import traceback
8
7
 
9
8
  from fastapi import FastAPI
10
9
  from fastapi.exceptions import HTTPException
@@ -251,9 +250,6 @@ class ShapeshifterService():
251
250
  f"An error occurred during the post-processing of a {message.__class__.__name__} message."
252
251
  f"{err.__class__.__name__}: {err}"
253
252
  )
254
- logger.error(
255
- traceback.format_exc()
256
- )
257
253
  finally:
258
254
  self.inbound_queue.task_done()
259
255
 
@@ -265,13 +265,6 @@ class DsoPortfolioQueryResponse(PayloadMessageResponse):
265
265
  class Meta:
266
266
  name = "DSOPortfolioQueryResponse"
267
267
 
268
- congestion_point: Optional[DsoPortfolioQueryCongestionPoint] = field(
269
- default=None,
270
- metadata={
271
- "name": "CongestionPoint",
272
- "type": "Element",
273
- }
274
- )
275
268
  dso_portfolio_query_message_id: str = field(
276
269
  metadata={
277
270
  "name": "DSOPortfolioQueryMessageID",
@@ -280,6 +273,14 @@ class DsoPortfolioQueryResponse(PayloadMessageResponse):
280
273
  "pattern": r"[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}",
281
274
  }
282
275
  )
276
+
277
+ congestion_point: Optional[DsoPortfolioQueryCongestionPoint] = field(
278
+ default=None,
279
+ metadata={
280
+ "name": "CongestionPoint",
281
+ "type": "Element",
282
+ }
283
+ )
283
284
  time_zone: str = field(
284
285
  default=DEFAULT_TIME_ZONE,
285
286
  metadata={
@@ -0,0 +1,8 @@
1
+ from datetime import datetime
2
+ import re
3
+
4
+
5
+ def parse_uftp_timestamp(timestamp_str):
6
+ """
7
+ Parses a UFTP timestamp into a Python datetime.datetime object. This can take care of the
8
+ """
@@ -1,29 +1,32 @@
1
- from decimal import Decimal, InvalidOperation
2
-
3
-
4
- def validate_decimal(name: str, value: int | float | Decimal | str, digits: int):
5
- """
6
- Validates that the decimal is acceptable, and returns it with the correct number of digits.
7
- """
8
- if isinstance(value, str):
9
- try:
10
- value = Decimal(value)
11
- except InvalidOperation as exc:
12
- raise ValueError(f"{name} must be a valid numeric value, not '{value}'") from exc
13
- if not isinstance(value, (int, float, Decimal)):
14
- raise TypeError(f"'{name}' must be a numeric type, not {type(value)}")
15
- return Decimal(f"{value:.{digits}f}")
16
-
17
-
18
- def validate_list(name, value, item_type, length):
19
- """
20
- Validates that the list is of the correct type, length and content type.
21
- """
22
- if not isinstance(value, list):
23
- raise TypeError(f"'{name}' must be a list, not {type(value)}")
24
- if len(value) < length:
25
- raise ValueError(f"'Length of list '{name}' must be {length} or greater, not {len(value)}")
26
- # if not all(isinstance(item, item_type) for item in value):
27
- # raise TypeError(f"'Not all items in list were of type {item_type}")
28
- return value
29
-
1
+ from decimal import Decimal, InvalidOperation
2
+
3
+
4
+ def validate_decimal(name: str, value: int | float | Decimal | str, digits: int):
5
+ """
6
+ Validates that the decimal is acceptable, and returns it with the correct number of digits.
7
+ """
8
+ if isinstance(value, str):
9
+ try:
10
+ value = Decimal(value)
11
+ except InvalidOperation as exc:
12
+ raise ValueError(f"{name} must be a valid numeric value, not '{value}'") from exc
13
+ if not isinstance(value, (int, float, Decimal)):
14
+ raise TypeError(f"'{name}' must be a numeric type, not {type(value)}")
15
+ return Decimal(f"{value:.{digits}f}")
16
+
17
+
18
+ def validate_list(name, value, item_type, length):
19
+ """
20
+ Validates that the list is of the correct type, length and content type.
21
+ """
22
+ if not isinstance(value, list):
23
+ raise TypeError(f"'{name}' must be a list, not {type(value)}")
24
+ if len(value) < length:
25
+ raise ValueError(f"'Length of list '{name}' must be {length} or greater, not {len(value)}")
26
+ for index, item in enumerate(value):
27
+ if not isinstance(item, item_type):
28
+ raise TypeError(
29
+ f"Not all items of property {name} were of type {item_type}: "
30
+ f"item at index {index} was of type {type(item)}"
31
+ )
32
+ return value
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: shapeshifter-uftp
3
- Version: 1.1.1.dev2
3
+ Version: 1.1.2
4
4
  Summary: Allows connections between DSO, AGR and CRO using the Shapeshifter (UFTP) protocol.
5
5
  Requires-Python: >=3.10
6
6
  Provides-Extra: dev
@@ -1,4 +1,5 @@
1
1
  README.rst
2
+ pyproject.toml
2
3
  setup.py
3
4
  shapeshifter_uftp/__init__.py
4
5
  shapeshifter_uftp/cli.py
@@ -31,6 +32,7 @@ shapeshifter_uftp/uftp/common.py
31
32
  shapeshifter_uftp/uftp/cro_dso.py
32
33
  shapeshifter_uftp/uftp/defaults.py
33
34
  shapeshifter_uftp/uftp/metering.py
35
+ shapeshifter_uftp/uftp/parsing.py
34
36
  shapeshifter_uftp/uftp/validations.py
35
37
  test/test_cli.py
36
38
  test/test_client_errors.py
@@ -1,8 +1,8 @@
1
- xsdata[lxml]
2
- pynacl
3
- dnspython
1
+ xsdata[lxml]==24.3.1
2
+ pynacl==1.5.0
3
+ dnspython==2.6.1
4
4
  fastapi<0.100
5
- fastapi-xml
5
+ fastapi-xml==1.0.0b1
6
6
  requests
7
7
  uvicorn
8
8
  termcolor
@@ -1,57 +1,54 @@
1
- import pytest
2
- from time import sleep
3
- from concurrent.futures import ThreadPoolExecutor
4
-
5
- from shapeshifter_uftp.uftp import routing_map, PayloadMessageResponse, AcceptedRejected
6
- from shapeshifter_uftp.service.base_service import snake_case
7
- from .helpers.messages import messages
8
- from .helpers.services import DummyAgrService, DummyCroService, DummyDsoService
9
-
10
-
11
- # These fixtures allow us to only start up the services once and use
12
- # them for all the parametrized test cases, which speeds up testing.
13
- @pytest.fixture(scope='module')
14
- def agr_service():
15
- with DummyAgrService() as service:
16
- yield service
17
-
18
- @pytest.fixture(scope='module')
19
- def cro_service():
20
- with DummyCroService() as service:
21
- yield service
22
-
23
- @pytest.fixture(scope='module')
24
- def dso_service():
25
- with DummyDsoService() as service:
26
- yield service
27
-
28
-
29
- @pytest.mark.parametrize(
30
- 'message',
31
- messages,
32
- ids=[message.__class__.__name__ for message in messages]
33
- )
34
- def test_communications(message, agr_service, cro_service, dso_service):
35
- service_map = {
36
- "AGR": agr_service,
37
- "CRO": cro_service,
38
- "DSO": dso_service,
39
- }
40
-
41
- sender_role, recipient_role = routing_map[type(message)]
42
- sender = service_map[sender_role]
43
- recipient = service_map[recipient_role]
44
- client_method = f"{recipient.sender_role.lower()}_client"
45
- with ThreadPoolExecutor() as executor:
46
- with getattr(sender, client_method)(recipient.sender_domain) as client:
47
- sending_method = f"send_{snake_case(message.__class__.__name__)}"
48
- main_future = executor.submit(getattr(client, sending_method), message)
49
-
50
- parsed_message = recipient.request_futures[f"pre_process_{snake_case(message.__class__.__name__)}"].result()
51
- breakpoint()
52
- assert parsed_message == message
53
-
54
- response_message = PayloadMessageResponse()
55
- recipient.response_futures[f"pre_process_{snake_case(message.__class__.__name__)}"].set_result(response_message)
56
- assert recipient.request_futures[f"process_{snake_case(message.__class__.__name__)}"].result() == message
57
- assert main_future.result().result == AcceptedRejected.ACCEPTED
1
+ import pytest
2
+ from time import sleep
3
+ from concurrent.futures import ThreadPoolExecutor
4
+
5
+ from shapeshifter_uftp.uftp import routing_map, PayloadMessageResponse, AcceptedRejected
6
+ from shapeshifter_uftp.service.base_service import snake_case
7
+ from .helpers.messages import messages
8
+ from .helpers.services import DummyAgrService, DummyCroService, DummyDsoService
9
+
10
+
11
+ # These fixtures allow us to only start up the services once and use
12
+ # them for all the parametrized test cases, which speeds up testing.
13
+ @pytest.fixture(scope='module')
14
+ def agr_service():
15
+ with DummyAgrService() as service:
16
+ yield service
17
+
18
+ @pytest.fixture(scope='module')
19
+ def cro_service():
20
+ with DummyCroService() as service:
21
+ yield service
22
+
23
+ @pytest.fixture(scope='module')
24
+ def dso_service():
25
+ with DummyDsoService() as service:
26
+ yield service
27
+
28
+
29
+ @pytest.mark.parametrize(
30
+ 'message',
31
+ messages,
32
+ ids=[message.__class__.__name__ for message in messages]
33
+ )
34
+ def test_communications(message, agr_service, cro_service, dso_service):
35
+ service_map = {
36
+ "AGR": agr_service,
37
+ "CRO": cro_service,
38
+ "DSO": dso_service,
39
+ }
40
+
41
+ sender_role, recipient_role = routing_map[type(message)]
42
+ sender = service_map[sender_role]
43
+ recipient = service_map[recipient_role]
44
+ client_method = f"{recipient.sender_role.lower()}_client"
45
+ with ThreadPoolExecutor() as executor:
46
+ with getattr(sender, client_method)(recipient.sender_domain) as client:
47
+ sending_method = f"send_{snake_case(message.__class__.__name__)}"
48
+ main_future = executor.submit(getattr(client, sending_method), message)
49
+
50
+ assert recipient.request_futures[f"pre_process_{snake_case(message.__class__.__name__)}"].result() == message
51
+ response_message = PayloadMessageResponse()
52
+ recipient.response_futures[f"pre_process_{snake_case(message.__class__.__name__)}"].set_result(response_message)
53
+ assert recipient.request_futures[f"process_{snake_case(message.__class__.__name__)}"].result() == message
54
+ assert main_future.result().result == AcceptedRejected.ACCEPTED