otdf-python 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

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 (96) hide show
  1. otdf_python/__init__.py +1 -2
  2. otdf_python/__main__.py +1 -2
  3. otdf_python/address_normalizer.py +8 -10
  4. otdf_python/aesgcm.py +8 -0
  5. otdf_python/assertion_config.py +21 -0
  6. otdf_python/asym_crypto.py +18 -22
  7. otdf_python/auth_headers.py +7 -6
  8. otdf_python/autoconfigure_utils.py +21 -7
  9. otdf_python/cli.py +5 -5
  10. otdf_python/collection_store.py +13 -1
  11. otdf_python/collection_store_impl.py +5 -0
  12. otdf_python/config.py +13 -0
  13. otdf_python/connect_client.py +1 -0
  14. otdf_python/constants.py +2 -0
  15. otdf_python/crypto_utils.py +4 -0
  16. otdf_python/dpop.py +3 -5
  17. otdf_python/ecc_constants.py +12 -14
  18. otdf_python/ecc_mode.py +7 -2
  19. otdf_python/ecdh.py +24 -31
  20. otdf_python/eckeypair.py +5 -0
  21. otdf_python/header.py +5 -0
  22. otdf_python/invalid_zip_exception.py +6 -2
  23. otdf_python/kas_client.py +66 -55
  24. otdf_python/kas_connect_rpc_client.py +75 -38
  25. otdf_python/kas_info.py +4 -3
  26. otdf_python/kas_key_cache.py +10 -9
  27. otdf_python/key_type.py +4 -0
  28. otdf_python/key_type_constants.py +4 -11
  29. otdf_python/manifest.py +24 -0
  30. otdf_python/nanotdf.py +30 -28
  31. otdf_python/nanotdf_ecdsa_struct.py +5 -11
  32. otdf_python/nanotdf_type.py +13 -1
  33. otdf_python/policy_binding_serializer.py +6 -4
  34. otdf_python/policy_info.py +6 -0
  35. otdf_python/policy_object.py +8 -0
  36. otdf_python/policy_stub.py +2 -0
  37. otdf_python/resource_locator.py +22 -13
  38. otdf_python/sdk.py +51 -73
  39. otdf_python/sdk_builder.py +60 -47
  40. otdf_python/sdk_exceptions.py +11 -1
  41. otdf_python/symmetric_and_payload_config.py +6 -0
  42. otdf_python/tdf.py +47 -10
  43. otdf_python/tdf_reader.py +10 -13
  44. otdf_python/tdf_writer.py +5 -0
  45. otdf_python/token_source.py +4 -3
  46. otdf_python/version.py +5 -0
  47. otdf_python/zip_reader.py +10 -2
  48. otdf_python/zip_writer.py +11 -0
  49. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/METADATA +3 -2
  50. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/RECORD +81 -72
  51. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/WHEEL +1 -1
  52. otdf_python_proto/__init__.py +2 -6
  53. otdf_python_proto/authorization/__init__.py +10 -0
  54. otdf_python_proto/authorization/authorization_connect.py +250 -0
  55. otdf_python_proto/authorization/v2/authorization_connect.py +315 -0
  56. otdf_python_proto/entityresolution/__init__.py +10 -0
  57. otdf_python_proto/entityresolution/entity_resolution_connect.py +185 -0
  58. otdf_python_proto/entityresolution/v2/entity_resolution_connect.py +185 -0
  59. otdf_python_proto/kas/__init__.py +2 -2
  60. otdf_python_proto/kas/kas_connect.py +259 -0
  61. otdf_python_proto/policy/actions/__init__.py +11 -0
  62. otdf_python_proto/policy/actions/actions_connect.py +380 -0
  63. otdf_python_proto/policy/attributes/__init__.py +11 -0
  64. otdf_python_proto/policy/attributes/attributes_connect.py +1310 -0
  65. otdf_python_proto/policy/kasregistry/__init__.py +11 -0
  66. otdf_python_proto/policy/kasregistry/key_access_server_registry_connect.py +912 -0
  67. otdf_python_proto/policy/keymanagement/__init__.py +11 -0
  68. otdf_python_proto/policy/keymanagement/key_management_connect.py +380 -0
  69. otdf_python_proto/policy/namespaces/__init__.py +11 -0
  70. otdf_python_proto/policy/namespaces/namespaces_connect.py +648 -0
  71. otdf_python_proto/policy/registeredresources/__init__.py +11 -0
  72. otdf_python_proto/policy/registeredresources/registered_resources_connect.py +770 -0
  73. otdf_python_proto/policy/resourcemapping/__init__.py +11 -0
  74. otdf_python_proto/policy/resourcemapping/resource_mapping_connect.py +790 -0
  75. otdf_python_proto/policy/subjectmapping/__init__.py +11 -0
  76. otdf_python_proto/policy/subjectmapping/subject_mapping_connect.py +851 -0
  77. otdf_python_proto/policy/unsafe/__init__.py +11 -0
  78. otdf_python_proto/policy/unsafe/unsafe_connect.py +705 -0
  79. otdf_python_proto/wellknownconfiguration/__init__.py +10 -0
  80. otdf_python_proto/wellknownconfiguration/wellknown_configuration_connect.py +124 -0
  81. otdf_python_proto/authorization/authorization_pb2_connect.py +0 -191
  82. otdf_python_proto/authorization/v2/authorization_pb2_connect.py +0 -233
  83. otdf_python_proto/entityresolution/entity_resolution_pb2_connect.py +0 -149
  84. otdf_python_proto/entityresolution/v2/entity_resolution_pb2_connect.py +0 -149
  85. otdf_python_proto/kas/kas_pb2_connect.py +0 -192
  86. otdf_python_proto/policy/actions/actions_pb2_connect.py +0 -275
  87. otdf_python_proto/policy/attributes/attributes_pb2_connect.py +0 -863
  88. otdf_python_proto/policy/kasregistry/key_access_server_registry_pb2_connect.py +0 -611
  89. otdf_python_proto/policy/keymanagement/key_management_pb2_connect.py +0 -275
  90. otdf_python_proto/policy/namespaces/namespaces_pb2_connect.py +0 -443
  91. otdf_python_proto/policy/registeredresources/registered_resources_pb2_connect.py +0 -527
  92. otdf_python_proto/policy/resourcemapping/resource_mapping_pb2_connect.py +0 -527
  93. otdf_python_proto/policy/subjectmapping/subject_mapping_pb2_connect.py +0 -569
  94. otdf_python_proto/policy/unsafe/unsafe_pb2_connect.py +0 -485
  95. otdf_python_proto/wellknownconfiguration/wellknown_configuration_pb2_connect.py +0 -107
  96. {otdf_python-0.4.0.dist-info → otdf_python-0.4.2.dist-info}/licenses/LICENSE +0 -0
otdf_python/__init__.py CHANGED
@@ -1,5 +1,4 @@
1
- """
2
- OpenTDF Python SDK
1
+ """OpenTDF Python SDK.
3
2
 
4
3
  A Python implementation of the OpenTDF SDK for working with Trusted Data Format (TDF) files.
5
4
  Provides both programmatic APIs and command-line interface for encryption and decryption.
otdf_python/__main__.py CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- Main entry point for running otdf_python as a module.
2
+ """Main entry point for running otdf_python as a module.
4
3
 
5
4
  This allows the package to be run with `python -m otdf_python` and properly
6
5
  handles the CLI interface without import conflicts.
@@ -1,6 +1,4 @@
1
- """
2
- Address normalization utilities for OpenTDF.
3
- """
1
+ """Address normalization utilities for OpenTDF."""
4
2
 
5
3
  import logging
6
4
  import re
@@ -12,8 +10,7 @@ logger = logging.getLogger(__name__)
12
10
 
13
11
 
14
12
  def normalize_address(url_string: str, use_plaintext: bool) -> str:
15
- """
16
- Normalize a URL address to ensure it has the correct scheme and port.
13
+ """Normalize a URL address to ensure it has the correct scheme and port.
17
14
 
18
15
  Args:
19
16
  url_string: The URL string to normalize
@@ -24,6 +21,7 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str:
24
21
 
25
22
  Raises:
26
23
  SDKException: If there's an error parsing or creating the URL
24
+
27
25
  """
28
26
  scheme = "http" if use_plaintext else "https"
29
27
 
@@ -34,8 +32,8 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str:
34
32
  port_str = host_port_pattern.group(2)
35
33
  try:
36
34
  port = int(port_str)
37
- except ValueError:
38
- raise SDKException(f"Invalid port in URL [{url_string}]")
35
+ except ValueError as err:
36
+ raise SDKException(f"Invalid port in URL [{url_string}]") from err
39
37
 
40
38
  normalized_url = f"{scheme}://{host}:{port}"
41
39
  logger.debug(f"normalized url [{url_string}] to [{normalized_url}]")
@@ -66,8 +64,8 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str:
66
64
  _, port_str = parsed_url.netloc.split(":", 1)
67
65
  try:
68
66
  port = int(port_str)
69
- except ValueError:
70
- raise SDKException(f"Invalid port in URL [{url_string}]")
67
+ except ValueError as err:
68
+ raise SDKException(f"Invalid port in URL [{url_string}]") from err
71
69
 
72
70
  # If no port was found or extracted, use the default
73
71
  if port is None:
@@ -81,4 +79,4 @@ def normalize_address(url_string: str, use_plaintext: bool) -> str:
81
79
  except Exception as e:
82
80
  if isinstance(e, SDKException):
83
81
  raise e
84
- raise SDKException(f"Error normalizing URL [{url_string}]", e)
82
+ raise SDKException(f"Error normalizing URL [{url_string}]: {e}") from e
otdf_python/aesgcm.py CHANGED
@@ -1,13 +1,18 @@
1
+ """AES-GCM encryption and decryption functionality."""
2
+
1
3
  import os
2
4
 
3
5
  from cryptography.hazmat.primitives.ciphers.aead import AESGCM
4
6
 
5
7
 
6
8
  class AesGcm:
9
+ """AES-GCM encryption and decryption operations."""
10
+
7
11
  GCM_NONCE_LENGTH = 12
8
12
  GCM_TAG_LENGTH = 16
9
13
 
10
14
  def __init__(self, key: bytes):
15
+ """Initialize AES-GCM cipher with key."""
11
16
  if not key or len(key) not in (16, 24, 32):
12
17
  raise ValueError("Invalid key size for GCM encryption")
13
18
  self.key = key
@@ -17,7 +22,10 @@ class AesGcm:
17
22
  return self.key
18
23
 
19
24
  class Encrypted:
25
+ """Encrypted data with initialization vector and ciphertext."""
26
+
20
27
  def __init__(self, iv: bytes, ciphertext: bytes):
28
+ """Initialize encrypted data."""
21
29
  self.iv = iv
22
30
  self.ciphertext = ciphertext
23
31
 
@@ -1,8 +1,12 @@
1
+ """Assertion configuration for TDF."""
2
+
1
3
  from enum import Enum, auto
2
4
  from typing import Any
3
5
 
4
6
 
5
7
  class Type(Enum):
8
+ """Assertion type enumeration."""
9
+
6
10
  HANDLING_ASSERTION = "handling"
7
11
  BASE_ASSERTION = "base"
8
12
 
@@ -11,6 +15,8 @@ class Type(Enum):
11
15
 
12
16
 
13
17
  class Scope(Enum):
18
+ """Assertion scope enumeration."""
19
+
14
20
  TRUSTED_DATA_OBJ = "tdo"
15
21
  PAYLOAD = "payload"
16
22
 
@@ -19,12 +25,16 @@ class Scope(Enum):
19
25
 
20
26
 
21
27
  class AssertionKeyAlg(Enum):
28
+ """Assertion key algorithm enumeration."""
29
+
22
30
  RS256 = auto()
23
31
  HS256 = auto()
24
32
  NOT_DEFINED = auto()
25
33
 
26
34
 
27
35
  class AppliesToState(Enum):
36
+ """Assertion applies-to state enumeration."""
37
+
28
38
  ENCRYPTED = "encrypted"
29
39
  UNENCRYPTED = "unencrypted"
30
40
 
@@ -33,6 +43,8 @@ class AppliesToState(Enum):
33
43
 
34
44
 
35
45
  class BindingMethod(Enum):
46
+ """Assertion binding method enumeration."""
47
+
36
48
  JWS = "jws"
37
49
 
38
50
  def __str__(self):
@@ -40,7 +52,10 @@ class BindingMethod(Enum):
40
52
 
41
53
 
42
54
  class AssertionKey:
55
+ """Assertion signing key configuration."""
56
+
43
57
  def __init__(self, alg: AssertionKeyAlg, key: Any):
58
+ """Initialize assertion key."""
44
59
  self.alg = alg
45
60
  self.key = key
46
61
 
@@ -49,7 +64,10 @@ class AssertionKey:
49
64
 
50
65
 
51
66
  class Statement:
67
+ """Assertion statement with format, schema, and value."""
68
+
52
69
  def __init__(self, format: str, schema: str, value: str):
70
+ """Initialize assertion statement."""
53
71
  self.format = format
54
72
  self.schema = schema
55
73
  self.value = value
@@ -67,6 +85,8 @@ class Statement:
67
85
 
68
86
 
69
87
  class AssertionConfig:
88
+ """TDF assertion configuration."""
89
+
70
90
  def __init__(
71
91
  self,
72
92
  id: str,
@@ -76,6 +96,7 @@ class AssertionConfig:
76
96
  statement: Statement,
77
97
  signing_key: AssertionKey | None = None,
78
98
  ):
99
+ """Initialize assertion configuration."""
79
100
  self.id = id
80
101
  self.type = type
81
102
  self.scope = scope
@@ -1,6 +1,4 @@
1
- """
2
- Asymmetric encryption and decryption utilities for RSA keys in PEM format.
3
- """
1
+ """Asymmetric encryption and decryption utilities for RSA keys in PEM format."""
4
2
 
5
3
  import base64
6
4
  import re
@@ -14,8 +12,7 @@ from .sdk_exceptions import SDKException
14
12
 
15
13
 
16
14
  class AsymDecryption:
17
- """
18
- Provides functionality for asymmetric decryption using an RSA private key.
15
+ """Provides functionality for asymmetric decryption using an RSA private key.
19
16
 
20
17
  Supports both PEM string and key object initialization for flexibility.
21
18
  """
@@ -25,8 +22,7 @@ class AsymDecryption:
25
22
  PRIVATE_KEY_FOOTER = "-----END PRIVATE KEY-----"
26
23
 
27
24
  def __init__(self, private_key_pem: str | None = None, private_key_obj=None):
28
- """
29
- Initialize with either a PEM string or a key object.
25
+ """Initialize with either a PEM string or a key object.
30
26
 
31
27
  Args:
32
28
  private_key_pem: Private key in PEM format (with or without headers)
@@ -34,6 +30,7 @@ class AsymDecryption:
34
30
 
35
31
  Raises:
36
32
  SDKException: If key loading fails
33
+
37
34
  """
38
35
  if private_key_obj is not None:
39
36
  self.private_key = private_key_obj
@@ -60,13 +57,12 @@ class AsymDecryption:
60
57
  decoded, password=None, backend=default_backend()
61
58
  )
62
59
  except Exception as e:
63
- raise SDKException(f"Failed to load private key: {e}")
60
+ raise SDKException(f"Failed to load private key: {e}") from e
64
61
  else:
65
62
  self.private_key = None
66
63
 
67
64
  def decrypt(self, data: bytes) -> bytes:
68
- """
69
- Decrypt data using RSA OAEP with SHA-1.
65
+ """Decrypt data using RSA OAEP with SHA-1.
70
66
 
71
67
  Args:
72
68
  data: Encrypted bytes to decrypt
@@ -76,6 +72,7 @@ class AsymDecryption:
76
72
 
77
73
  Raises:
78
74
  SDKException: If decryption fails or key is not set
75
+
79
76
  """
80
77
  if self.private_key is None:
81
78
  raise SDKException("Failed to decrypt, private key is empty")
@@ -89,12 +86,11 @@ class AsymDecryption:
89
86
  ),
90
87
  )
91
88
  except Exception as e:
92
- raise SDKException(f"Error performing decryption: {e}")
89
+ raise SDKException(f"Error performing decryption: {e}") from e
93
90
 
94
91
 
95
92
  class AsymEncryption:
96
- """
97
- Provides functionality for asymmetric encryption using an RSA public key or certificate in PEM format.
93
+ """Provides functionality for asymmetric encryption using an RSA public key or certificate in PEM format.
98
94
 
99
95
  Supports PEM public keys, X.509 certificates, and pre-loaded key objects.
100
96
  Also handles base64-encoded keys without PEM headers.
@@ -105,8 +101,7 @@ class AsymEncryption:
105
101
  CIPHER_TRANSFORM = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
106
102
 
107
103
  def __init__(self, public_key_pem: str | None = None, public_key_obj=None):
108
- """
109
- Initialize with either a PEM string or a key object.
104
+ """Initialize with either a PEM string or a key object.
110
105
 
111
106
  Args:
112
107
  public_key_pem: Public key in PEM format, X.509 certificate, or base64 string
@@ -114,6 +109,7 @@ class AsymEncryption:
114
109
 
115
110
  Raises:
116
111
  SDKException: If key loading fails or key is not RSA
112
+
117
113
  """
118
114
  if public_key_obj is not None:
119
115
  self.public_key = public_key_obj
@@ -141,7 +137,7 @@ class AsymEncryption:
141
137
  decoded, backend=default_backend()
142
138
  )
143
139
  except Exception as e:
144
- raise SDKException(f"Failed to load public key: {e}")
140
+ raise SDKException(f"Failed to load public key: {e}") from e
145
141
  else:
146
142
  self.public_key = None
147
143
 
@@ -152,8 +148,7 @@ class AsymEncryption:
152
148
  raise SDKException("Not an RSA PEM formatted public key")
153
149
 
154
150
  def encrypt(self, data: bytes) -> bytes:
155
- """
156
- Encrypt data using RSA OAEP with SHA-1.
151
+ """Encrypt data using RSA OAEP with SHA-1.
157
152
 
158
153
  Args:
159
154
  data: Plaintext bytes to encrypt
@@ -163,6 +158,7 @@ class AsymEncryption:
163
158
 
164
159
  Raises:
165
160
  SDKException: If encryption fails or key is not set
161
+
166
162
  """
167
163
  if self.public_key is None:
168
164
  raise SDKException("Failed to encrypt, public key is empty")
@@ -176,17 +172,17 @@ class AsymEncryption:
176
172
  ),
177
173
  )
178
174
  except Exception as e:
179
- raise SDKException(f"Error performing encryption: {e}")
175
+ raise SDKException(f"Error performing encryption: {e}") from e
180
176
 
181
177
  def public_key_in_pem_format(self) -> str:
182
- """
183
- Export the public key to PEM format.
178
+ """Export the public key to PEM format.
184
179
 
185
180
  Returns:
186
181
  Public key as PEM-encoded string
187
182
 
188
183
  Raises:
189
184
  SDKException: If export fails
185
+
190
186
  """
191
187
  try:
192
188
  pem = self.public_key.public_bytes(
@@ -195,4 +191,4 @@ class AsymEncryption:
195
191
  )
196
192
  return pem.decode()
197
193
  except Exception as e:
198
- raise SDKException(f"Error exporting public key to PEM: {e}")
194
+ raise SDKException(f"Error exporting public key to PEM: {e}") from e
@@ -1,10 +1,11 @@
1
+ """Authentication header management."""
2
+
1
3
  from dataclasses import dataclass
2
4
 
3
5
 
4
6
  @dataclass
5
7
  class AuthHeaders:
6
- """
7
- Represents authentication headers used in token-based authorization.
8
+ """Represents authentication headers used in token-based authorization.
8
9
  This class holds authorization and DPoP (Demonstrating Proof of Possession) headers
9
10
  that are used in token-based API requests.
10
11
  """
@@ -13,19 +14,19 @@ class AuthHeaders:
13
14
  dpop_header: str = ""
14
15
 
15
16
  def get_auth_header(self) -> str:
16
- """Returns the authorization header."""
17
+ """Get the authorization header."""
17
18
  return self.auth_header
18
19
 
19
20
  def get_dpop_header(self) -> str:
20
- """Returns the DPoP header."""
21
+ """Get the DPoP header."""
21
22
  return self.dpop_header
22
23
 
23
24
  def to_dict(self) -> dict[str, str]:
24
- """
25
- Convert authentication headers to a dictionary for use with HTTP clients.
25
+ """Convert authentication headers to a dictionary for use with HTTP clients.
26
26
 
27
27
  Returns:
28
28
  Dictionary with 'Authorization' header and optionally 'DPoP' header
29
+
29
30
  """
30
31
  headers = {"Authorization": self.auth_header}
31
32
  if self.dpop_header:
@@ -1,3 +1,5 @@
1
+ """Utilities for automatic SDK configuration."""
2
+
1
3
  import re
2
4
  import urllib.parse
3
5
  from dataclasses import dataclass
@@ -6,6 +8,8 @@ from typing import Any
6
8
 
7
9
  # RuleType constants
8
10
  class RuleType:
11
+ """Rule type constants for attribute hierarchy."""
12
+
9
13
  HIERARCHY = "hierarchy"
10
14
  ALL_OF = "allOf"
11
15
  ANY_OF = "anyOf"
@@ -15,6 +19,8 @@ class RuleType:
15
19
 
16
20
  @dataclass(frozen=True)
17
21
  class KeySplitStep:
22
+ """Key split step information."""
23
+
18
24
  kas: str
19
25
  splitID: str
20
26
 
@@ -31,21 +37,24 @@ class KeySplitStep:
31
37
 
32
38
 
33
39
  class AutoConfigureException(Exception):
34
- pass
40
+ """Exception for auto-configuration errors."""
35
41
 
36
42
 
37
43
  class AttributeNameFQN:
44
+ """Fully qualified attribute name."""
45
+
38
46
  def __init__(self, url: str):
47
+ """Initialize attribute name from URL."""
39
48
  pattern = re.compile(r"^(https?://[\w./-]+)/attr/([^/\s]*)$")
40
49
  matcher = pattern.match(url)
41
50
  if not matcher or not matcher.group(1) or not matcher.group(2):
42
51
  raise AutoConfigureException("invalid type: attribute regex fail")
43
52
  try:
44
53
  urllib.parse.unquote(matcher.group(2))
45
- except Exception:
54
+ except Exception as err:
46
55
  raise AutoConfigureException(
47
56
  f"invalid type: error in attribute name [{matcher.group(2)}]"
48
- )
57
+ ) from err
49
58
  self.url = url
50
59
  self.key = url.lower()
51
60
 
@@ -76,12 +85,15 @@ class AttributeNameFQN:
76
85
  raise AutoConfigureException("invalid attribute")
77
86
  try:
78
87
  return urllib.parse.unquote(matcher.group(1))
79
- except Exception:
80
- raise AutoConfigureException("invalid type")
88
+ except Exception as err:
89
+ raise AutoConfigureException("invalid type") from err
81
90
 
82
91
 
83
92
  class AttributeValueFQN:
93
+ """Fully qualified attribute value."""
94
+
84
95
  def __init__(self, url: str):
96
+ """Initialize attribute value from URL."""
85
97
  pattern = re.compile(r"^(https?://[\w./-]+)/attr/(\S*)/value/(\S*)$")
86
98
  matcher = pattern.match(url)
87
99
  if (
@@ -96,8 +108,10 @@ class AttributeValueFQN:
96
108
  try:
97
109
  urllib.parse.unquote(matcher.group(2))
98
110
  urllib.parse.unquote(matcher.group(3))
99
- except Exception:
100
- raise AutoConfigureException("invalid type: error in attribute or value")
111
+ except Exception as err:
112
+ raise AutoConfigureException(
113
+ "invalid type: error in attribute or value"
114
+ ) from err
101
115
  self.url = url
102
116
  self.key = url.lower()
103
117
 
otdf_python/cli.py CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python3
2
- """
3
- OpenTDF Python CLI
2
+ """OpenTDF Python CLI.
4
3
 
5
4
  A command-line interface for encrypting and decrypting files using OpenTDF.
6
5
  Provides encrypt, decrypt, and inspect commands similar to the otdfctl CLI.
@@ -36,6 +35,7 @@ class CLIError(Exception):
36
35
  """Custom exception for CLI errors."""
37
36
 
38
37
  def __init__(self, level: str, message: str, cause: Exception | None = None):
38
+ """Initialize CLI error."""
39
39
  self.level = level
40
40
  self.message = message
41
41
  self.cause = cause
@@ -105,11 +105,11 @@ def load_client_credentials(creds_file_path: str) -> tuple[str, str]:
105
105
  except json.JSONDecodeError as e:
106
106
  raise CLIError(
107
107
  "CRITICAL", f"Invalid JSON in credentials file {creds_file_path}: {e}"
108
- )
108
+ ) from e
109
109
  except Exception as e:
110
110
  raise CLIError(
111
111
  "CRITICAL", f"Error reading credentials file {creds_file_path}: {e}"
112
- )
112
+ ) from e
113
113
 
114
114
 
115
115
  def build_sdk(args) -> SDK:
@@ -525,7 +525,7 @@ Where creds.json contains:
525
525
 
526
526
 
527
527
  def main():
528
- """Main CLI entry point."""
528
+ """Execute the CLI entry point."""
529
529
  parser = create_parser()
530
530
  args = parser.parse_args()
531
531
 
@@ -1,12 +1,19 @@
1
+ """Collection store interface for managing collections."""
2
+
1
3
  from collections import OrderedDict
2
4
 
3
5
 
4
6
  class CollectionKey:
7
+ """Collection key wrapper for store operations."""
8
+
5
9
  def __init__(self, key: bytes | None):
10
+ """Initialize collection key."""
6
11
  self.key = key
7
12
 
8
13
 
9
14
  class CollectionStore:
15
+ """Abstract collection store interface for key management."""
16
+
10
17
  NO_PRIVATE_KEY = CollectionKey(None)
11
18
 
12
19
  def store(self, header, key: CollectionKey):
@@ -17,17 +24,22 @@ class CollectionStore:
17
24
 
18
25
 
19
26
  class NoOpCollectionStore(CollectionStore):
27
+ """No-op collection store that discards all keys."""
28
+
20
29
  def store(self, header, key: CollectionKey):
21
- pass
30
+ """Discard key operation (no-op)."""
22
31
 
23
32
  def get_key(self, header) -> CollectionKey:
24
33
  return self.NO_PRIVATE_KEY
25
34
 
26
35
 
27
36
  class CollectionStoreImpl(OrderedDict, CollectionStore):
37
+ """Collection store implementation with ordered dictionary."""
38
+
28
39
  MAX_SIZE_STORE = 500
29
40
 
30
41
  def __init__(self):
42
+ """Initialize collection store."""
31
43
  super().__init__()
32
44
 
33
45
  def store(self, header, key: CollectionKey):
@@ -1,3 +1,5 @@
1
+ """Collection store implementation."""
2
+
1
3
  from collections import OrderedDict
2
4
  from threading import RLock
3
5
 
@@ -5,7 +7,10 @@ MAX_SIZE_STORE = 500
5
7
 
6
8
 
7
9
  class CollectionStoreImpl(OrderedDict):
10
+ """Thread-safe collection store for caching TDF keys."""
11
+
8
12
  def __init__(self):
13
+ """Initialize collection store."""
9
14
  super().__init__()
10
15
  self._lock = RLock()
11
16
 
otdf_python/config.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Configuration classes for TDF and NanoTDF operations."""
2
+
1
3
  from dataclasses import dataclass, field
2
4
  from enum import Enum
3
5
  from typing import Any
@@ -5,17 +7,23 @@ from urllib.parse import urlparse, urlunparse
5
7
 
6
8
 
7
9
  class TDFFormat(Enum):
10
+ """TDF format enumeration."""
11
+
8
12
  JSONFormat = "JSONFormat"
9
13
  XMLFormat = "XMLFormat"
10
14
 
11
15
 
12
16
  class IntegrityAlgorithm(Enum):
17
+ """Integrity algorithm enumeration."""
18
+
13
19
  HS256 = "HS256"
14
20
  GMAC = "GMAC"
15
21
 
16
22
 
17
23
  @dataclass
18
24
  class KASInfo:
25
+ """Key Access Service information."""
26
+
19
27
  url: str
20
28
  public_key: str | None = None
21
29
  kid: str | None = None
@@ -28,6 +36,8 @@ class KASInfo:
28
36
 
29
37
  @dataclass
30
38
  class TDFConfig:
39
+ """TDF encryption configuration."""
40
+
31
41
  autoconfigure: bool = True
32
42
  default_segment_size: int = 2 * 1024 * 1024
33
43
  enable_encryption: bool = True
@@ -49,6 +59,8 @@ class TDFConfig:
49
59
 
50
60
  @dataclass
51
61
  class NanoTDFConfig:
62
+ """NanoTDF encryption configuration."""
63
+
52
64
  ecc_mode: str | None = None
53
65
  cipher: str | None = None
54
66
  config: str | None = None
@@ -60,6 +72,7 @@ class NanoTDFConfig:
60
72
 
61
73
  # Utility function to normalize KAS URLs (Python equivalent)
62
74
  def get_kas_address(kas_url: str) -> str:
75
+ """Normalize KAS URL by adding https:// if no scheme present."""
63
76
  if "://" not in kas_url:
64
77
  kas_url = "https://" + kas_url
65
78
  parsed = urlparse(kas_url)
@@ -0,0 +1 @@
1
+ """Connect RPC client for KAS operations."""
otdf_python/constants.py CHANGED
@@ -1 +1,3 @@
1
+ """Application constants and default values."""
2
+
1
3
  MAGIC_NUMBER_AND_VERSION = bytes([0x4C, 0x31, 0x4C])
@@ -1,3 +1,5 @@
1
+ """Cryptographic utility functions."""
2
+
1
3
  import hashlib
2
4
  import hmac
3
5
 
@@ -7,6 +9,8 @@ from cryptography.hazmat.primitives.asymmetric import ec, rsa
7
9
 
8
10
 
9
11
  class CryptoUtils:
12
+ """Cryptographic utility functions and helpers."""
13
+
10
14
  KEYPAIR_SIZE = 2048
11
15
 
12
16
  @staticmethod
otdf_python/dpop.py CHANGED
@@ -1,6 +1,4 @@
1
- """
2
- DPoP (Demonstration of Proof-of-Possession) token generation utilities.
3
- """
1
+ """DPoP (Demonstration of Proof-of-Possession) token generation utilities."""
4
2
 
5
3
  import base64
6
4
  import hashlib
@@ -18,8 +16,7 @@ def create_dpop_token(
18
16
  method: str = "POST",
19
17
  access_token: str | None = None,
20
18
  ) -> str:
21
- """
22
- Create a DPoP (Demonstration of Proof-of-Possession) token.
19
+ """Create a DPoP (Demonstration of Proof-of-Possession) token.
23
20
 
24
21
  Args:
25
22
  private_key_pem: RSA private key in PEM format for signing
@@ -30,6 +27,7 @@ def create_dpop_token(
30
27
 
31
28
  Returns:
32
29
  DPoP token as a string
30
+
33
31
  """
34
32
  # Parse the RSA public key to extract modulus and exponent
35
33
  public_key_obj = CryptoUtils.get_rsa_public_key_from_pem(public_key_pem)