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.
- digitea_licensing-1.1.1/PKG-INFO +7 -0
- digitea_licensing-1.1.1/README.md +11 -0
- digitea_licensing-1.1.1/digitea_licensing/__init__.py +5 -0
- digitea_licensing-1.1.1/digitea_licensing/client.py +101 -0
- digitea_licensing-1.1.1/digitea_licensing/errors.py +32 -0
- digitea_licensing-1.1.1/digitea_licensing/storage.py +47 -0
- digitea_licensing-1.1.1/digitea_licensing.egg-info/PKG-INFO +7 -0
- digitea_licensing-1.1.1/digitea_licensing.egg-info/SOURCES.txt +30 -0
- digitea_licensing-1.1.1/digitea_licensing.egg-info/dependency_links.txt +1 -0
- digitea_licensing-1.1.1/digitea_licensing.egg-info/requires.txt +2 -0
- digitea_licensing-1.1.1/digitea_licensing.egg-info/top_level.txt +2 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/__init__.py +68 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/api/__init__.py +5 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/api/runtime_api.py +1272 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/api_client.py +833 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/api_response.py +21 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/configuration.py +645 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/exceptions.py +218 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/__init__.py +26 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/activate_input.py +95 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/activation_result.py +120 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/activation_result_activation.py +91 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/deactivation_result.py +106 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/error.py +105 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/error_error.py +91 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/licensing_error_code.py +51 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/renewal_session_result.py +109 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/runtime_input.py +93 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/models/validation_result.py +110 -0
- digitea_licensing-1.1.1/generated/digitea_licensing_generated/rest.py +334 -0
- digitea_licensing-1.1.1/pyproject.toml +14 -0
- digitea_licensing-1.1.1/setup.cfg +4 -0
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -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
|
+
|