digitea-licensing 1.1.1__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 (32) hide show
  1. digitea_licensing-1.1.1/PKG-INFO +7 -0
  2. digitea_licensing-1.1.1/README.md +11 -0
  3. digitea_licensing-1.1.1/digitea_licensing/__init__.py +5 -0
  4. digitea_licensing-1.1.1/digitea_licensing/client.py +101 -0
  5. digitea_licensing-1.1.1/digitea_licensing/errors.py +32 -0
  6. digitea_licensing-1.1.1/digitea_licensing/storage.py +47 -0
  7. digitea_licensing-1.1.1/digitea_licensing.egg-info/PKG-INFO +7 -0
  8. digitea_licensing-1.1.1/digitea_licensing.egg-info/SOURCES.txt +30 -0
  9. digitea_licensing-1.1.1/digitea_licensing.egg-info/dependency_links.txt +1 -0
  10. digitea_licensing-1.1.1/digitea_licensing.egg-info/requires.txt +2 -0
  11. digitea_licensing-1.1.1/digitea_licensing.egg-info/top_level.txt +2 -0
  12. digitea_licensing-1.1.1/generated/digitea_licensing_generated/__init__.py +68 -0
  13. digitea_licensing-1.1.1/generated/digitea_licensing_generated/api/__init__.py +5 -0
  14. digitea_licensing-1.1.1/generated/digitea_licensing_generated/api/runtime_api.py +1272 -0
  15. digitea_licensing-1.1.1/generated/digitea_licensing_generated/api_client.py +833 -0
  16. digitea_licensing-1.1.1/generated/digitea_licensing_generated/api_response.py +21 -0
  17. digitea_licensing-1.1.1/generated/digitea_licensing_generated/configuration.py +645 -0
  18. digitea_licensing-1.1.1/generated/digitea_licensing_generated/exceptions.py +218 -0
  19. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/__init__.py +26 -0
  20. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/activate_input.py +95 -0
  21. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/activation_result.py +120 -0
  22. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/activation_result_activation.py +91 -0
  23. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/deactivation_result.py +106 -0
  24. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/error.py +105 -0
  25. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/error_error.py +91 -0
  26. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/licensing_error_code.py +51 -0
  27. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/renewal_session_result.py +109 -0
  28. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/runtime_input.py +93 -0
  29. digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/validation_result.py +110 -0
  30. digitea_licensing-1.1.1/generated/digitea_licensing_generated/rest.py +334 -0
  31. digitea_licensing-1.1.1/pyproject.toml +14 -0
  32. digitea_licensing-1.1.1/setup.cfg +4 -0
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: digitea-licensing
3
+ Version: 1.1.1
4
+ Summary: Digitea Runtime licensing client
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: pydantic<3,>=2
7
+ Requires-Dist: urllib3<3,>=2
@@ -0,0 +1,11 @@
1
+ # Digitea Licensing Python SDK
2
+
3
+ The public facade is DigiteaLicensing. The generated OpenAPI transport under generated/ is an implementation detail.
4
+
5
+ Example:
6
+
7
+ from digitea_licensing import DigiteaLicensing
8
+ client = DigiteaLicensing(product_id="prd_example123")
9
+ result = client.activate("dgt_lic_example", instance_id="stable-id")
10
+
11
+ The renewal method generates an idempotency key. Pass the same idempotency_key when retrying a logical operation.
@@ -0,0 +1,5 @@
1
+ from .client import DigiteaLicensing
2
+ from .errors import DigiteaError
3
+ from .storage import FileInstanceIdStore, InstanceIdStore
4
+
5
+ __all__ = ["DigiteaLicensing", "DigiteaError", "FileInstanceIdStore", "InstanceIdStore"]
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import secrets
5
+ import time
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ _generated_root = str(Path(__file__).resolve().parents[1] / "generated")
11
+ if _generated_root not in sys.path:
12
+ sys.path.insert(0, _generated_root)
13
+
14
+ from digitea_licensing_generated.api.runtime_api import RuntimeApi
15
+ from digitea_licensing_generated.api_client import ApiClient
16
+ from digitea_licensing_generated.configuration import Configuration
17
+ from digitea_licensing_generated.models.activate_input import ActivateInput
18
+ from digitea_licensing_generated.models.runtime_input import RuntimeInput
19
+
20
+ from .errors import from_exception
21
+ from .storage import FileInstanceIdStore, InstanceIdStore, get_or_create
22
+
23
+ _KEY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]*$")
24
+ _RETRYABLE = {408, 429, 500, 502, 503, 504}
25
+
26
+
27
+ class DigiteaLicensing:
28
+ def __init__(
29
+ self,
30
+ product_id: str,
31
+ base_url: str = "https://api.getdigitea.com",
32
+ *,
33
+ instance_id: str | None = None,
34
+ instance_store: InstanceIdStore | None = None,
35
+ timeout: float = 10.0,
36
+ retries: int = 2,
37
+ api: Any | None = None,
38
+ ):
39
+ if not product_id:
40
+ raise ValueError("product_id is required")
41
+ if timeout <= 0 or retries < 0:
42
+ raise ValueError("timeout must be positive and retries must be non-negative")
43
+ self.product_id = product_id
44
+ self.timeout = timeout
45
+ self.retries = retries
46
+ self.instance_store = instance_store or FileInstanceIdStore()
47
+ self.instance_id = instance_id or get_or_create(self.instance_store)
48
+ if api is None:
49
+ configuration = Configuration(host=base_url.rstrip("/"))
50
+ api = RuntimeApi(ApiClient(configuration))
51
+ self._api = api
52
+
53
+ def activate(self, license_key: str, *, instance_id: str | None = None, instance_name: str | None = None):
54
+ payload = ActivateInput(
55
+ license_key=license_key,
56
+ product_id=self.product_id,
57
+ instance_id=instance_id or self.instance_id,
58
+ instance_name=instance_name,
59
+ )
60
+ return self._call(lambda: self._api.activate_license(payload, _request_timeout=self.timeout))
61
+
62
+ def validate(self, license_key: str, *, instance_id: str | None = None):
63
+ payload = self._runtime_input(license_key, instance_id)
64
+ return self._call(lambda: self._api.validate_license(payload, _request_timeout=self.timeout))
65
+
66
+ def deactivate(self, license_key: str, *, instance_id: str | None = None):
67
+ payload = self._runtime_input(license_key, instance_id)
68
+ return self._call(lambda: self._api.deactivate_license(payload, _request_timeout=self.timeout))
69
+
70
+ def create_renewal_session(
71
+ self,
72
+ license_key: str,
73
+ *,
74
+ instance_id: str | None = None,
75
+ idempotency_key: str | None = None,
76
+ ):
77
+ key = idempotency_key or ("rnl_" + secrets.token_urlsafe(24))
78
+ if not _KEY_RE.fullmatch(key) or not 8 <= len(key) <= 128:
79
+ raise ValueError("idempotency_key must match the Digitea contract")
80
+ payload = self._runtime_input(license_key, instance_id)
81
+ return self._call(lambda: self._api.create_renewal_session(
82
+ payload, _request_timeout=self.timeout, _headers={"Idempotency-Key": key}
83
+ ))
84
+
85
+ def _runtime_input(self, license_key: str, instance_id: str | None):
86
+ return RuntimeInput(
87
+ license_key=license_key,
88
+ product_id=self.product_id,
89
+ instance_id=instance_id or self.instance_id,
90
+ )
91
+
92
+ def _call(self, operation):
93
+ for attempt in range(self.retries + 1):
94
+ try:
95
+ return operation()
96
+ except Exception as raw:
97
+ error = from_exception(raw)
98
+ if attempt >= self.retries or (error.status is not None and error.status not in _RETRYABLE):
99
+ raise error from raw
100
+ time.sleep(min(2.0, 0.2 * (2 ** attempt)))
101
+ raise AssertionError("unreachable")
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+
7
+ class DigiteaError(RuntimeError):
8
+ def __init__(self, code: str, message: str, status: int | None = None, details: Any = None):
9
+ super().__init__(message)
10
+ self.code = code
11
+ self.status = status
12
+ self.details = details
13
+
14
+
15
+ def from_exception(exc: Exception) -> DigiteaError:
16
+ status = getattr(exc, "status", None) or getattr(exc, "code", None)
17
+ body = getattr(exc, "body", None)
18
+ payload = None
19
+ if isinstance(body, (bytes, bytearray)):
20
+ body = body.decode("utf-8", "replace")
21
+ if isinstance(body, str):
22
+ try:
23
+ payload = json.loads(body)
24
+ except json.JSONDecodeError:
25
+ payload = None
26
+ if isinstance(payload, dict):
27
+ error = payload.get("error") if isinstance(payload.get("error"), dict) else payload
28
+ code = str(error.get("code") or "API_ERROR")
29
+ message = str(error.get("message") or "Digitea licensing request failed")
30
+ return DigiteaError(code, message, int(status) if status else None, error.get("details"))
31
+ code = "NETWORK_ERROR" if not status else "API_ERROR"
32
+ return DigiteaError(code, str(exc) or "Digitea licensing request failed", int(status) if status else None)
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import secrets
5
+ from pathlib import Path
6
+ from typing import Protocol
7
+
8
+
9
+ class InstanceIdStore(Protocol):
10
+ def load(self) -> str | None: ...
11
+ def save(self, instance_id: str) -> None: ...
12
+
13
+
14
+ class FileInstanceIdStore:
15
+ def __init__(self, path: str | os.PathLike[str] | None = None):
16
+ default = Path.home() / ".digitea" / "instance-id"
17
+ self.path = Path(path or os.environ.get("DIGITEA_INSTANCE_FILE", default))
18
+
19
+ def load(self) -> str | None:
20
+ try:
21
+ value = self.path.read_text(encoding="utf-8").strip()
22
+ except FileNotFoundError:
23
+ return None
24
+ return value or None
25
+
26
+ def save(self, instance_id: str) -> None:
27
+ self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
28
+ temporary = self.path.with_suffix(".tmp")
29
+ temporary.write_text(instance_id + "\n", encoding="utf-8")
30
+ try:
31
+ os.chmod(temporary, 0o600)
32
+ except OSError:
33
+ pass
34
+ os.replace(temporary, self.path)
35
+
36
+
37
+ def new_instance_id() -> str:
38
+ return "inst_" + secrets.token_urlsafe(24)
39
+
40
+
41
+ def get_or_create(store: InstanceIdStore) -> str:
42
+ existing = store.load()
43
+ if existing:
44
+ return existing
45
+ value = new_instance_id()
46
+ store.save(value)
47
+ return value
@@ -0,0 +1,7 @@
1
+ Metadata-Version: 2.4
2
+ Name: digitea-licensing
3
+ Version: 1.1.1
4
+ Summary: Digitea Runtime licensing client
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: pydantic<3,>=2
7
+ Requires-Dist: urllib3<3,>=2
@@ -0,0 +1,30 @@
1
+ README.md
2
+ pyproject.toml
3
+ digitea_licensing/__init__.py
4
+ digitea_licensing/client.py
5
+ digitea_licensing/errors.py
6
+ digitea_licensing/storage.py
7
+ digitea_licensing.egg-info/PKG-INFO
8
+ digitea_licensing.egg-info/SOURCES.txt
9
+ digitea_licensing.egg-info/dependency_links.txt
10
+ digitea_licensing.egg-info/requires.txt
11
+ digitea_licensing.egg-info/top_level.txt
12
+ generated/digitea_licensing_generated/__init__.py
13
+ generated/digitea_licensing_generated/api_client.py
14
+ generated/digitea_licensing_generated/api_response.py
15
+ generated/digitea_licensing_generated/configuration.py
16
+ generated/digitea_licensing_generated/exceptions.py
17
+ generated/digitea_licensing_generated/rest.py
18
+ generated/digitea_licensing_generated/api/__init__.py
19
+ generated/digitea_licensing_generated/api/runtime_api.py
20
+ generated/digitea_licensing_generated/models/__init__.py
21
+ generated/digitea_licensing_generated/models/activate_input.py
22
+ generated/digitea_licensing_generated/models/activation_result.py
23
+ generated/digitea_licensing_generated/models/activation_result_activation.py
24
+ generated/digitea_licensing_generated/models/deactivation_result.py
25
+ generated/digitea_licensing_generated/models/error.py
26
+ generated/digitea_licensing_generated/models/error_error.py
27
+ generated/digitea_licensing_generated/models/licensing_error_code.py
28
+ generated/digitea_licensing_generated/models/renewal_session_result.py
29
+ generated/digitea_licensing_generated/models/runtime_input.py
30
+ generated/digitea_licensing_generated/models/validation_result.py
@@ -0,0 +1,2 @@
1
+ pydantic<3,>=2
2
+ urllib3<3,>=2
@@ -0,0 +1,2 @@
1
+ digitea_licensing
2
+ digitea_licensing_generated
@@ -0,0 +1,68 @@
1
+ # coding: utf-8
2
+
3
+ # flake8: noqa
4
+
5
+ """
6
+ Digitea Licensing Runtime API
7
+
8
+ Public Runtime v1 contract. Distributed software uses productId, licenseKey and instanceId. No vendor credential is accepted or required.
9
+
10
+ The version of the OpenAPI document: 1.1.0
11
+ Generated by OpenAPI Generator (https://openapi-generator.tech)
12
+
13
+ Do not edit the class manually.
14
+ """ # noqa: E501
15
+
16
+
17
+ __version__ = "0.1.0"
18
+
19
+ # Define package exports
20
+ __all__ = [
21
+ "RuntimeApi",
22
+ "ApiResponse",
23
+ "ApiClient",
24
+ "Configuration",
25
+ "OpenApiException",
26
+ "ApiTypeError",
27
+ "ApiValueError",
28
+ "ApiKeyError",
29
+ "ApiAttributeError",
30
+ "ApiException",
31
+ "ActivateInput",
32
+ "ActivationResult",
33
+ "ActivationResultActivation",
34
+ "DeactivationResult",
35
+ "Error",
36
+ "ErrorError",
37
+ "LicensingErrorCode",
38
+ "RenewalSessionResult",
39
+ "RuntimeInput",
40
+ "ValidationResult",
41
+ ]
42
+
43
+ # import apis into sdk package
44
+ from digitea_licensing_generated.api.runtime_api import RuntimeApi as RuntimeApi
45
+
46
+ # import ApiClient
47
+ from digitea_licensing_generated.api_response import ApiResponse as ApiResponse
48
+ from digitea_licensing_generated.api_client import ApiClient as ApiClient
49
+ from digitea_licensing_generated.configuration import Configuration as Configuration
50
+ from digitea_licensing_generated.exceptions import OpenApiException as OpenApiException
51
+ from digitea_licensing_generated.exceptions import ApiTypeError as ApiTypeError
52
+ from digitea_licensing_generated.exceptions import ApiValueError as ApiValueError
53
+ from digitea_licensing_generated.exceptions import ApiKeyError as ApiKeyError
54
+ from digitea_licensing_generated.exceptions import ApiAttributeError as ApiAttributeError
55
+ from digitea_licensing_generated.exceptions import ApiException as ApiException
56
+
57
+ # import models into sdk package
58
+ from digitea_licensing_generated.models.activate_input import ActivateInput as ActivateInput
59
+ from digitea_licensing_generated.models.activation_result import ActivationResult as ActivationResult
60
+ from digitea_licensing_generated.models.activation_result_activation import ActivationResultActivation as ActivationResultActivation
61
+ from digitea_licensing_generated.models.deactivation_result import DeactivationResult as DeactivationResult
62
+ from digitea_licensing_generated.models.error import Error as Error
63
+ from digitea_licensing_generated.models.error_error import ErrorError as ErrorError
64
+ from digitea_licensing_generated.models.licensing_error_code import LicensingErrorCode as LicensingErrorCode
65
+ from digitea_licensing_generated.models.renewal_session_result import RenewalSessionResult as RenewalSessionResult
66
+ from digitea_licensing_generated.models.runtime_input import RuntimeInput as RuntimeInput
67
+ from digitea_licensing_generated.models.validation_result import ValidationResult as ValidationResult
68
+
@@ -0,0 +1,5 @@
1
+ # flake8: noqa
2
+
3
+ # import apis into api package
4
+ from digitea_licensing_generated.api.runtime_api import RuntimeApi
5
+