qlam-core 0.1.0__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.
- qlam_core/__init__.py +52 -0
- qlam_core/auth/__init__.py +40 -0
- qlam_core/auth/base.py +191 -0
- qlam_core/auth/client.py +562 -0
- qlam_core/auth/credentials/__init__.py +13 -0
- qlam_core/auth/credentials/credentials_cache.py +399 -0
- qlam_core/auth/credentials/credentials_cache_models.py +39 -0
- qlam_core/auth/credentials/models.py +51 -0
- qlam_core/auth/oauth/__init__.py +1 -0
- qlam_core/auth/oauth/constants.py +36 -0
- qlam_core/auth/oauth/exceptions.py +54 -0
- qlam_core/auth/oauth/flows.py +1048 -0
- qlam_core/auth/oauth/models.py +131 -0
- qlam_core/auth/oauth/redaction.py +51 -0
- qlam_core/auth/providers/__init__.py +1 -0
- qlam_core/auth/providers/basic.py +339 -0
- qlam_core/auth/providers/local.py +121 -0
- qlam_core/auth/providers/oauth.py +392 -0
- qlam_core/auth/registry.py +448 -0
- qlam_core/clients/__init__.py +13 -0
- qlam_core/clients/http/__init__.py +11 -0
- qlam_core/clients/http/_constants.py +11 -0
- qlam_core/clients/http/client.py +535 -0
- qlam_core/clients/http/resolver.py +450 -0
- qlam_core/clients/http/universal_executor.py +394 -0
- qlam_core/common/__init__.py +36 -0
- qlam_core/common/api_models.py +139 -0
- qlam_core/common/api_response.py +53 -0
- qlam_core/common/context.py +148 -0
- qlam_core/common/typing.py +14 -0
- qlam_core/common/version.py +23 -0
- qlam_core/common/visibility.py +10 -0
- qlam_core/config/__init__.py +5 -0
- qlam_core/config/config.py +344 -0
- qlam_core/config/config_models.py +303 -0
- qlam_core/errors/__init__.py +25 -0
- qlam_core/errors/errors.py +101 -0
- qlam_core/errors/exit_codes.py +88 -0
- qlam_core/paths/__init__.py +19 -0
- qlam_core/paths/paths.py +203 -0
- qlam_core/plugins/__init__.py +24 -0
- qlam_core/plugins/auth/__init__.py +10 -0
- qlam_core/plugins/common/__init__.py +20 -0
- qlam_core/plugins/common/mixins.py +784 -0
- qlam_core/plugins/compilations/__init__.py +10 -0
- qlam_core/plugins/compilations/api/__init__.py +26 -0
- qlam_core/plugins/compilations/api/client.py +82 -0
- qlam_core/plugins/compilations/api/compilations_models.py +135 -0
- qlam_core/plugins/compilations/api/compilations_paths.py +8 -0
- qlam_core/plugins/compilations/api/compilations_spec.py +121 -0
- qlam_core/plugins/definitions/__init__.py +10 -0
- qlam_core/plugins/definitions/api/__init__.py +24 -0
- qlam_core/plugins/definitions/api/client.py +82 -0
- qlam_core/plugins/definitions/api/definitions_models.py +175 -0
- qlam_core/plugins/definitions/api/definitions_paths.py +7 -0
- qlam_core/plugins/definitions/api/definitions_spec.py +99 -0
- qlam_core/plugins/protocols.py +251 -0
- qlam_core/plugins/results/__init__.py +10 -0
- qlam_core/plugins/results/api/__init__.py +5 -0
- qlam_core/plugins/results/api/client.py +65 -0
- qlam_core/plugins/results/api/result_manager_spec.py +69 -0
- qlam_core/plugins/results/api/result_transformer_spec.py +75 -0
- qlam_core/plugins/tasks/__init__.py +10 -0
- qlam_core/plugins/tasks/api/__init__.py +16 -0
- qlam_core/plugins/tasks/api/client.py +120 -0
- qlam_core/plugins/tasks/api/tasks_models.py +234 -0
- qlam_core/plugins/tasks/api/tasks_paths.py +8 -0
- qlam_core/plugins/tasks/api/tasks_spec.py +121 -0
- qlam_core/py.typed +0 -0
- qlam_core/sdk/__init__.py +40 -0
- qlam_core/sdk/auth.py +45 -0
- qlam_core/sdk/base_api.py +126 -0
- qlam_core/sdk/decorators.py +382 -0
- qlam_core/sdk/executor.py +32 -0
- qlam_core/sdk/http_executor.py +251 -0
- qlam_core/sdk/pagination.py +120 -0
- qlam_core-0.1.0.dist-info/METADATA +215 -0
- qlam_core-0.1.0.dist-info/RECORD +82 -0
- qlam_core-0.1.0.dist-info/WHEEL +5 -0
- qlam_core-0.1.0.dist-info/licenses/LICENSE +183 -0
- qlam_core-0.1.0.dist-info/licenses/THIRD_PARTY_LICENSES.md +1123 -0
- qlam_core-0.1.0.dist-info/top_level.txt +1 -0
qlam_core/__init__.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""qlam-core: Core library for QuEra Quantum Computing Services.
|
|
2
|
+
|
|
3
|
+
This package provides the foundational components for interacting with
|
|
4
|
+
QuEra's quantum computing services:
|
|
5
|
+
|
|
6
|
+
- **config**: Configuration management
|
|
7
|
+
- **auth**: Authentication providers and OAuth flows
|
|
8
|
+
- **clients**: HTTP client with authentication integration
|
|
9
|
+
- **common**: Shared utilities including AppContext
|
|
10
|
+
- **paths**: Path management abstraction
|
|
11
|
+
- **errors**: Error hierarchy and exit codes
|
|
12
|
+
- **sdk**: Base classes for API clients
|
|
13
|
+
- **plugins**: API clients for QuEra services (tasks, results, etc.)
|
|
14
|
+
|
|
15
|
+
Example usage:
|
|
16
|
+
|
|
17
|
+
from qlam_core.common import AppContext
|
|
18
|
+
from qlam_core.plugins.tasks import TasksClient
|
|
19
|
+
|
|
20
|
+
ctx = AppContext(context_name="production")
|
|
21
|
+
with TasksClient(ctx) as client:
|
|
22
|
+
tasks = client.list()
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from qlam_core.errors.errors import (
|
|
26
|
+
APIError,
|
|
27
|
+
AuthConfigurationError,
|
|
28
|
+
AuthenticationError,
|
|
29
|
+
ConfigurationError,
|
|
30
|
+
FileSystemError,
|
|
31
|
+
NetworkError,
|
|
32
|
+
QlamCoreError,
|
|
33
|
+
ValidationError,
|
|
34
|
+
)
|
|
35
|
+
from qlam_core.errors.exit_codes import ExitCode
|
|
36
|
+
from qlam_core.paths.paths import PathsProvider, QshPaths
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
# Errors
|
|
40
|
+
"QlamCoreError",
|
|
41
|
+
"AuthenticationError",
|
|
42
|
+
"AuthConfigurationError",
|
|
43
|
+
"ConfigurationError",
|
|
44
|
+
"APIError",
|
|
45
|
+
"ValidationError",
|
|
46
|
+
"NetworkError",
|
|
47
|
+
"FileSystemError",
|
|
48
|
+
"ExitCode",
|
|
49
|
+
# Paths
|
|
50
|
+
"PathsProvider",
|
|
51
|
+
"QshPaths",
|
|
52
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Authentication for qlam-core.
|
|
2
|
+
|
|
3
|
+
This module provides authentication infrastructure:
|
|
4
|
+
- BaseAuthProvider: Abstract base class for auth providers
|
|
5
|
+
- AuthClient: Programmatic authentication API
|
|
6
|
+
- AuthRegistry: Provider discovery and registration
|
|
7
|
+
- OAuth flows, credentials cache, and provider implementations
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AuthClient",
|
|
12
|
+
"AuthRegistry",
|
|
13
|
+
"BaseAuthProvider",
|
|
14
|
+
"LoginLogoutMixin",
|
|
15
|
+
"auth_registry",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def __getattr__(name: str):
|
|
20
|
+
"""Lazy imports to avoid circular dependencies."""
|
|
21
|
+
if name in ("BaseAuthProvider", "LoginLogoutMixin"):
|
|
22
|
+
from qlam_core.auth.base import BaseAuthProvider, LoginLogoutMixin
|
|
23
|
+
|
|
24
|
+
if name == "BaseAuthProvider":
|
|
25
|
+
return BaseAuthProvider
|
|
26
|
+
return LoginLogoutMixin
|
|
27
|
+
|
|
28
|
+
if name == "AuthClient":
|
|
29
|
+
from qlam_core.auth.client import AuthClient
|
|
30
|
+
|
|
31
|
+
return AuthClient
|
|
32
|
+
|
|
33
|
+
if name in ("AuthRegistry", "auth_registry"):
|
|
34
|
+
from qlam_core.auth.registry import AuthRegistry, auth_registry
|
|
35
|
+
|
|
36
|
+
if name == "AuthRegistry":
|
|
37
|
+
return AuthRegistry
|
|
38
|
+
return auth_registry
|
|
39
|
+
|
|
40
|
+
raise AttributeError(f"module 'qlam_core.auth' has no attribute '{name}'")
|
qlam_core/auth/base.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Authentication provider framework base classes and mixins
|
|
2
|
+
|
|
3
|
+
Implement custom auth providers by subclassing `BaseAuthProvider` and, where
|
|
4
|
+
appropriate, implementing the `HTTPHeadersMixin` to supply HTTP headers and
|
|
5
|
+
`LoginLogoutMixin` for interactive flows. Optional `TokenMixin` defines a
|
|
6
|
+
canonical way to expose tokens.
|
|
7
|
+
|
|
8
|
+
Creating a custom provider (example)::
|
|
9
|
+
|
|
10
|
+
from typing import Dict, Any
|
|
11
|
+
from qlam_core.auth.base import BaseAuthProvider, HTTPHeadersMixin, LoginLogoutMixin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MyTokenProvider(BaseAuthProvider, HTTPHeadersMixin, LoginLogoutMixin):
|
|
15
|
+
name = "mytoken" # used by registry
|
|
16
|
+
|
|
17
|
+
def __init__(self, context_name: str, token: str | None = None) -> None:
|
|
18
|
+
self._context_name = context_name
|
|
19
|
+
self._token = token
|
|
20
|
+
|
|
21
|
+
def is_authenticated(self) -> bool:
|
|
22
|
+
return bool(self._token)
|
|
23
|
+
|
|
24
|
+
def get_auth_context(self) -> JsonDict:
|
|
25
|
+
return {"context": self._context_name, "has_token": self.is_authenticated()}
|
|
26
|
+
|
|
27
|
+
def get_headers(self) -> Dict[str, str]:
|
|
28
|
+
return {"Authorization": f"Bearer {self._token}"} if self._token else {}
|
|
29
|
+
|
|
30
|
+
def login(self) -> None:
|
|
31
|
+
# Implement a real flow; for example, prompt the user or exchange credentials
|
|
32
|
+
self._token = "...obtained..."
|
|
33
|
+
|
|
34
|
+
Registering the provider:
|
|
35
|
+
|
|
36
|
+
- Either rely on dynamic discovery in ``qlam_core.auth.registry`` if placed under
|
|
37
|
+
``qlam_core.auth.providers``, or call ``auth_registry.register_factory("mytoken", factory)``.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
from __future__ import annotations
|
|
41
|
+
|
|
42
|
+
from abc import ABC, abstractmethod
|
|
43
|
+
from typing import Any, Dict, Protocol, runtime_checkable
|
|
44
|
+
from qlam_core.auth.credentials.models import CredentialBase
|
|
45
|
+
|
|
46
|
+
from qlam_core.common.typing import JsonDict
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class BaseAuthProvider(ABC):
|
|
50
|
+
"""Abstract base class implementing core AuthProvider interface.
|
|
51
|
+
|
|
52
|
+
Provides only the required methods - other capabilities are mixed in via protocols.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def description(self) -> str:
|
|
58
|
+
"""Human-readable description of this authentication provider.
|
|
59
|
+
|
|
60
|
+
:return: Description suitable for help text and user interfaces.
|
|
61
|
+
"""
|
|
62
|
+
...
|
|
63
|
+
|
|
64
|
+
@abstractmethod
|
|
65
|
+
def is_authenticated(self) -> bool:
|
|
66
|
+
"""Check if the provider has valid authentication.
|
|
67
|
+
|
|
68
|
+
:return: True if authenticated, False otherwise.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
@abstractmethod
|
|
73
|
+
def get_auth_context(self) -> JsonDict:
|
|
74
|
+
"""Get authentication context for use by clients.
|
|
75
|
+
|
|
76
|
+
:return: Dictionary describing authentication state and metadata.
|
|
77
|
+
"""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
@abstractmethod
|
|
81
|
+
def get_credential(self) -> CredentialBase | None:
|
|
82
|
+
"""Return a credential payload as a Pydantic model.
|
|
83
|
+
|
|
84
|
+
Providers may return a credential with a non-authenticated status
|
|
85
|
+
(e.g. ``status="Not authenticated"``) to convey metadata such as
|
|
86
|
+
refresh capability. Return ``None`` only when the provider has no
|
|
87
|
+
credential data at all.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def build_from_settings(
|
|
93
|
+
cls, *, context_name: str, provider_name: str | None, provider_config: Any
|
|
94
|
+
) -> "BaseAuthProvider":
|
|
95
|
+
"""Create provider instance from settings configuration.
|
|
96
|
+
|
|
97
|
+
:param context_name: Context name for the provider.
|
|
98
|
+
:param provider_name: Optional provider name for identification.
|
|
99
|
+
:param provider_config: Provider configuration object from settings.
|
|
100
|
+
:return: Configured provider instance.
|
|
101
|
+
:raises NotImplementedError: If provider doesn't implement this method.
|
|
102
|
+
"""
|
|
103
|
+
raise NotImplementedError(f"Provider {cls.__name__} must implement build_from_settings")
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def extra_info_from_config(cls, provider_config: Any) -> JsonDict:
|
|
107
|
+
"""Extract extra display information from provider configuration.
|
|
108
|
+
|
|
109
|
+
:param provider_config: Provider configuration object from settings.
|
|
110
|
+
:return: Dictionary of extra information for display purposes.
|
|
111
|
+
"""
|
|
112
|
+
return {}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@runtime_checkable
|
|
116
|
+
class HTTPHeadersMixin(Protocol):
|
|
117
|
+
"""Protocol for auth providers that can generate HTTP headers.
|
|
118
|
+
|
|
119
|
+
This protocol defines the interface for providers that can generate
|
|
120
|
+
authentication headers for HTTP requests. Implementation is left to
|
|
121
|
+
the concrete provider classes.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def get_headers(self) -> Dict[str, str]:
|
|
125
|
+
"""Get authentication headers for HTTP requests.
|
|
126
|
+
|
|
127
|
+
:return: Dictionary of HTTP headers for authentication
|
|
128
|
+
"""
|
|
129
|
+
...
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class LoginLogoutMixin:
|
|
133
|
+
"""Mixin providing default login/logout implementations."""
|
|
134
|
+
|
|
135
|
+
def login(self) -> None:
|
|
136
|
+
"""Default login implementation (no-op).
|
|
137
|
+
|
|
138
|
+
Override to implement provider-specific login logic.
|
|
139
|
+
"""
|
|
140
|
+
pass
|
|
141
|
+
|
|
142
|
+
def logout(self) -> None:
|
|
143
|
+
"""Default logout implementation (no-op).
|
|
144
|
+
|
|
145
|
+
Override to implement provider-specific logout logic.
|
|
146
|
+
"""
|
|
147
|
+
pass
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@runtime_checkable
|
|
151
|
+
class TokenMixin(Protocol):
|
|
152
|
+
"""Protocol for token-based authentication providers.
|
|
153
|
+
|
|
154
|
+
This protocol defines the interface for providers that work with
|
|
155
|
+
access tokens. Implementation of token management is left to
|
|
156
|
+
concrete provider classes.
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def token(self) -> str:
|
|
160
|
+
"""Return an access token to be injected into requests.
|
|
161
|
+
|
|
162
|
+
:return: Access token string.
|
|
163
|
+
"""
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class RefreshCredentialsMixin(ABC):
|
|
168
|
+
"""Mixin for auth providers that can refresh cached credentials.
|
|
169
|
+
|
|
170
|
+
Providers that support refresh should inherit this mixin and implement the
|
|
171
|
+
required methods. This gives us a clear, explicit contract (no attribute
|
|
172
|
+
introspection needed) and works well with dynamic provider loading.
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
@abstractmethod
|
|
176
|
+
def refresh_credentials(self, *, force: bool = False) -> bool:
|
|
177
|
+
"""Refresh credentials for this provider, updating the cache if needed."""
|
|
178
|
+
|
|
179
|
+
@abstractmethod
|
|
180
|
+
def can_refresh_credentials(self) -> bool:
|
|
181
|
+
"""Return True if cached credentials can be refreshed non-interactively right now."""
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def supports_refresh_credentials(provider: object) -> bool:
|
|
185
|
+
"""Return True if ``provider`` supports non-interactive credential refresh.
|
|
186
|
+
|
|
187
|
+
.. note::
|
|
188
|
+
This is intentionally a contract-based check: refresh-capable providers must
|
|
189
|
+
inherit :class:`~qlam_core.auth.base.RefreshCredentialsMixin`.
|
|
190
|
+
"""
|
|
191
|
+
return isinstance(provider, RefreshCredentialsMixin)
|