exonware-xwapi 0.9.0.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.
- exonware/__init__.py +1 -0
- exonware/xwapi/__init__.py +227 -0
- exonware/xwapi/action.py +46 -0
- exonware/xwapi/base.py +357 -0
- exonware/xwapi/client/__init__.py +12 -0
- exonware/xwapi/client/base.py +16 -0
- exonware/xwapi/client/engines/__init__.py +97 -0
- exonware/xwapi/client/engines/base.py +47 -0
- exonware/xwapi/client/engines/contracts.py +142 -0
- exonware/xwapi/client/engines/native.py +118 -0
- exonware/xwapi/client/xwclient.py +674 -0
- exonware/xwapi/common/__init__.py +91 -0
- exonware/xwapi/common/app.py +183 -0
- exonware/xwapi/common/openapi.py +196 -0
- exonware/xwapi/common/serialization.py +58 -0
- exonware/xwapi/common/utils/__init__.py +11 -0
- exonware/xwapi/common/utils/async_utils.py +99 -0
- exonware/xwapi/config.py +43 -0
- exonware/xwapi/contracts.py +206 -0
- exonware/xwapi/defs.py +41 -0
- exonware/xwapi/entity_store.py +109 -0
- exonware/xwapi/errors.py +371 -0
- exonware/xwapi/facade.py +551 -0
- exonware/xwapi/providers.py +240 -0
- exonware/xwapi/query.py +87 -0
- exonware/xwapi/schema/__init__.py +25 -0
- exonware/xwapi/schema/contracts.py +59 -0
- exonware/xwapi/schema/generator.py +57 -0
- exonware/xwapi/schema/graphql.py +67 -0
- exonware/xwapi/schema/validator.py +69 -0
- exonware/xwapi/serialization.py +22 -0
- exonware/xwapi/server/__init__.py +21 -0
- exonware/xwapi/server/admin/__init__.py +13 -0
- exonware/xwapi/server/admin/router.py +459 -0
- exonware/xwapi/server/base.py +16 -0
- exonware/xwapi/server/engines/__init__.py +199 -0
- exonware/xwapi/server/engines/base.py +94 -0
- exonware/xwapi/server/engines/contracts.py +214 -0
- exonware/xwapi/server/engines/email_store.py +220 -0
- exonware/xwapi/server/engines/fastapi.py +473 -0
- exonware/xwapi/server/engines/flask.py +338 -0
- exonware/xwapi/server/engines/graphql.py +319 -0
- exonware/xwapi/server/engines/grpc.py +329 -0
- exonware/xwapi/server/engines/http_base.py +156 -0
- exonware/xwapi/server/engines/imap.py +303 -0
- exonware/xwapi/server/engines/pop3.py +270 -0
- exonware/xwapi/server/engines/smtp.py +192 -0
- exonware/xwapi/server/engines/websocket.py +215 -0
- exonware/xwapi/server/governance/__init__.py +19 -0
- exonware/xwapi/server/governance/lockfile.py +196 -0
- exonware/xwapi/server/governance/registry.py +145 -0
- exonware/xwapi/server/http/__init__.py +8 -0
- exonware/xwapi/server/http/error_adapter.py +32 -0
- exonware/xwapi/server/middleware/__init__.py +28 -0
- exonware/xwapi/server/middleware/admin_auth.py +69 -0
- exonware/xwapi/server/middleware/api_token.py +210 -0
- exonware/xwapi/server/middleware/auth.py +149 -0
- exonware/xwapi/server/middleware/observability.py +100 -0
- exonware/xwapi/server/middleware/pause.py +47 -0
- exonware/xwapi/server/middleware/ratelimit.py +140 -0
- exonware/xwapi/server/middleware/tenant.py +70 -0
- exonware/xwapi/server/middleware/trace.py +56 -0
- exonware/xwapi/server/pipeline/__init__.py +18 -0
- exonware/xwapi/server/pipeline/manager.py +57 -0
- exonware/xwapi/server/pipeline/outbox.py +212 -0
- exonware/xwapi/server/pipeline/worker.py +126 -0
- exonware/xwapi/server/xwserver.py +1773 -0
- exonware/xwapi/token_management.py +245 -0
- exonware/xwapi/version.py +82 -0
- exonware_xwapi-0.9.0.2.dist-info/METADATA +176 -0
- exonware_xwapi-0.9.0.2.dist-info/RECORD +73 -0
- exonware_xwapi-0.9.0.2.dist-info/WHEEL +4 -0
- exonware_xwapi-0.9.0.2.dist-info/licenses/LICENSE +22 -0
exonware/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__path__ = __import__('pkgutil').extend_path(__path__, __name__)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#exonware/xwapi/src/exonware/xwapi/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
xwapi: Entity-to-Web-API Conversion Library
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: eXonware Backend Team
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.9.0.2
|
|
8
|
+
"""
|
|
9
|
+
# =============================================================================
|
|
10
|
+
# XWLAZY — GUIDE_00_MASTER: config_package_lazy_install_enabled (EARLY)
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# Dependency: exonware-xwlazy ([lazy] extra). Canonical import: exonware.xwlazy
|
|
13
|
+
try:
|
|
14
|
+
from exonware.xwlazy import config_package_lazy_install_enabled
|
|
15
|
+
|
|
16
|
+
config_package_lazy_install_enabled(
|
|
17
|
+
__package__ or "exonware.xwapi",
|
|
18
|
+
enabled=True,
|
|
19
|
+
mode="smart",
|
|
20
|
+
)
|
|
21
|
+
except ImportError:
|
|
22
|
+
# xwlazy not installed — omit [lazy] extra or install exonware-xwlazy for lazy mode.
|
|
23
|
+
pass
|
|
24
|
+
# Core imports - safe, no dependencies
|
|
25
|
+
from exonware.xwapi.version import __version__, __author__, __email__
|
|
26
|
+
# Config imports - safe, minimal dependencies
|
|
27
|
+
from exonware.xwapi.config import XWAPIConfig
|
|
28
|
+
# Base imports - safe, only imports contracts
|
|
29
|
+
# Import from xwapi.base for consistency (these re-export from server/client/base for backward compatibility)
|
|
30
|
+
from exonware.xwapi.base import AApiServer, AApiAgent, AApiServicesProvider
|
|
31
|
+
# Errors - direct import (no circular dependency since errors.py doesn't import from __init__.py)
|
|
32
|
+
from exonware.xwapi.errors import (
|
|
33
|
+
XWAPIError,
|
|
34
|
+
ValidationError,
|
|
35
|
+
AuthenticationError,
|
|
36
|
+
AuthorizationError,
|
|
37
|
+
NotFoundError,
|
|
38
|
+
RateLimitError,
|
|
39
|
+
InternalError,
|
|
40
|
+
OpenAPIGenerationError,
|
|
41
|
+
FastAPICreationError,
|
|
42
|
+
EntityMappingError,
|
|
43
|
+
OAuth2ConfigurationError,
|
|
44
|
+
EndpointConfigurationError,
|
|
45
|
+
ServerLifecycleError,
|
|
46
|
+
StorageUnavailableError,
|
|
47
|
+
ServicePausedError,
|
|
48
|
+
create_error_response,
|
|
49
|
+
xwapi_error_to_http_parts,
|
|
50
|
+
http_status_to_xwapi_error,
|
|
51
|
+
get_http_status_code,
|
|
52
|
+
error_to_http_response,
|
|
53
|
+
get_error_headers,
|
|
54
|
+
# HTTP Status Code Constants
|
|
55
|
+
HTTP_200_OK,
|
|
56
|
+
HTTP_201_CREATED,
|
|
57
|
+
HTTP_202_ACCEPTED,
|
|
58
|
+
HTTP_204_NO_CONTENT,
|
|
59
|
+
HTTP_400_BAD_REQUEST,
|
|
60
|
+
HTTP_401_UNAUTHORIZED,
|
|
61
|
+
HTTP_403_FORBIDDEN,
|
|
62
|
+
HTTP_404_NOT_FOUND,
|
|
63
|
+
HTTP_405_METHOD_NOT_ALLOWED,
|
|
64
|
+
HTTP_409_CONFLICT,
|
|
65
|
+
HTTP_422_UNPROCESSABLE_ENTITY,
|
|
66
|
+
HTTP_429_TOO_MANY_REQUESTS,
|
|
67
|
+
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
68
|
+
HTTP_501_NOT_IMPLEMENTED,
|
|
69
|
+
HTTP_502_BAD_GATEWAY,
|
|
70
|
+
HTTP_503_SERVICE_UNAVAILABLE,
|
|
71
|
+
HTTP_504_GATEWAY_TIMEOUT,
|
|
72
|
+
)
|
|
73
|
+
# Engine system - engine-agnostic: only interfaces and registry imported
|
|
74
|
+
# Specific engines (FastAPIServerEngine, etc.) should be imported directly from their modules
|
|
75
|
+
from exonware.xwapi.server.engines import (
|
|
76
|
+
IApiServerEngine,
|
|
77
|
+
AApiServerEngineBase,
|
|
78
|
+
ApiServerEngineRegistry,
|
|
79
|
+
api_server_engine_registry,
|
|
80
|
+
)
|
|
81
|
+
# App factory - direct import
|
|
82
|
+
from exonware.xwapi.common.app import (
|
|
83
|
+
create_app,
|
|
84
|
+
register_module,
|
|
85
|
+
add_version_router,
|
|
86
|
+
add_openapi_endpoints,
|
|
87
|
+
)
|
|
88
|
+
# Query parameter parsing is handled directly in engine implementations
|
|
89
|
+
# Use XWQuery directly for query execution - no wrapper needed
|
|
90
|
+
# Middleware - direct import
|
|
91
|
+
from exonware.xwapi.server.middleware import (
|
|
92
|
+
trace_middleware,
|
|
93
|
+
tenant_middleware,
|
|
94
|
+
rate_limit_middleware,
|
|
95
|
+
auth_middleware,
|
|
96
|
+
observability_middleware,
|
|
97
|
+
)
|
|
98
|
+
# OpenAPI - direct import
|
|
99
|
+
from exonware.xwapi.common.openapi import (
|
|
100
|
+
merge_openapi_schemas,
|
|
101
|
+
validate_openapi_schema,
|
|
102
|
+
export_openapi_schema,
|
|
103
|
+
add_openapi_metadata,
|
|
104
|
+
get_openapi_version,
|
|
105
|
+
)
|
|
106
|
+
# Server - direct import
|
|
107
|
+
from exonware.xwapi.server.xwserver import XWApiServer
|
|
108
|
+
from exonware.xwapi.server.pipeline import ActionPipelineManager, BackgroundWorker, InMemoryOutboxStore
|
|
109
|
+
from exonware.xwapi.providers import (
|
|
110
|
+
LocalAuthProvider,
|
|
111
|
+
XWAuthLibraryProvider,
|
|
112
|
+
InMemoryStorageProvider,
|
|
113
|
+
XWStorageProvider,
|
|
114
|
+
InMemoryPaymentProvider,
|
|
115
|
+
)
|
|
116
|
+
from exonware.xwapi.token_management import APITokenManager
|
|
117
|
+
from exonware.xwapi.contracts import IApiServer
|
|
118
|
+
# Agent - direct import
|
|
119
|
+
from exonware.xwapi.client.xwclient import XWApiAgent
|
|
120
|
+
from exonware.xwapi.contracts import IApiAgent, IApiServicesProvider
|
|
121
|
+
from exonware.xwapi.contracts import IAuthProvider, IStorageProvider, IPaymentProvider
|
|
122
|
+
# Agent engines - direct import
|
|
123
|
+
from exonware.xwapi.client.engines import (
|
|
124
|
+
IApiAgentEngine,
|
|
125
|
+
AgentType,
|
|
126
|
+
ApiAgentEngineRegistry,
|
|
127
|
+
api_agent_engine_registry,
|
|
128
|
+
NativeAgentEngine,
|
|
129
|
+
)
|
|
130
|
+
# Serialization - use XWData/XWSystem directly, no wrapper needed
|
|
131
|
+
# Facade - direct import (import last to ensure all dependencies are loaded)
|
|
132
|
+
from exonware.xwapi.facade import XWAPI
|
|
133
|
+
__all__ = [
|
|
134
|
+
"__version__",
|
|
135
|
+
"__author__",
|
|
136
|
+
"__email__",
|
|
137
|
+
# Facade
|
|
138
|
+
"XWAPI",
|
|
139
|
+
"XWAPIConfig",
|
|
140
|
+
# App factory
|
|
141
|
+
"create_app",
|
|
142
|
+
"register_module",
|
|
143
|
+
"add_version_router",
|
|
144
|
+
"add_openapi_endpoints",
|
|
145
|
+
# Errors
|
|
146
|
+
"XWAPIError",
|
|
147
|
+
"ValidationError",
|
|
148
|
+
"AuthenticationError",
|
|
149
|
+
"AuthorizationError",
|
|
150
|
+
"NotFoundError",
|
|
151
|
+
"RateLimitError",
|
|
152
|
+
"InternalError",
|
|
153
|
+
"OpenAPIGenerationError",
|
|
154
|
+
"FastAPICreationError",
|
|
155
|
+
"EntityMappingError",
|
|
156
|
+
"OAuth2ConfigurationError",
|
|
157
|
+
"EndpointConfigurationError",
|
|
158
|
+
"ServerLifecycleError",
|
|
159
|
+
"StorageUnavailableError",
|
|
160
|
+
"ServicePausedError",
|
|
161
|
+
"create_error_response",
|
|
162
|
+
"xwapi_error_to_http_parts",
|
|
163
|
+
"http_status_to_xwapi_error",
|
|
164
|
+
"get_http_status_code",
|
|
165
|
+
"error_to_http_response",
|
|
166
|
+
"get_error_headers",
|
|
167
|
+
# HTTP Status Code Constants
|
|
168
|
+
"HTTP_200_OK",
|
|
169
|
+
"HTTP_201_CREATED",
|
|
170
|
+
"HTTP_202_ACCEPTED",
|
|
171
|
+
"HTTP_204_NO_CONTENT",
|
|
172
|
+
"HTTP_400_BAD_REQUEST",
|
|
173
|
+
"HTTP_401_UNAUTHORIZED",
|
|
174
|
+
"HTTP_403_FORBIDDEN",
|
|
175
|
+
"HTTP_404_NOT_FOUND",
|
|
176
|
+
"HTTP_405_METHOD_NOT_ALLOWED",
|
|
177
|
+
"HTTP_409_CONFLICT",
|
|
178
|
+
"HTTP_422_UNPROCESSABLE_ENTITY",
|
|
179
|
+
"HTTP_429_TOO_MANY_REQUESTS",
|
|
180
|
+
"HTTP_500_INTERNAL_SERVER_ERROR",
|
|
181
|
+
"HTTP_501_NOT_IMPLEMENTED",
|
|
182
|
+
"HTTP_502_BAD_GATEWAY",
|
|
183
|
+
"HTTP_503_SERVICE_UNAVAILABLE",
|
|
184
|
+
"HTTP_504_GATEWAY_TIMEOUT",
|
|
185
|
+
# Serialization - use XWData/XWSystem directly
|
|
186
|
+
# Server management - XWApiServer (extends AApiServer)
|
|
187
|
+
"XWApiServer",
|
|
188
|
+
"ActionPipelineManager",
|
|
189
|
+
"BackgroundWorker",
|
|
190
|
+
"InMemoryOutboxStore",
|
|
191
|
+
"APITokenManager",
|
|
192
|
+
"LocalAuthProvider",
|
|
193
|
+
"XWAuthLibraryProvider",
|
|
194
|
+
"InMemoryStorageProvider",
|
|
195
|
+
"XWStorageProvider",
|
|
196
|
+
"InMemoryPaymentProvider",
|
|
197
|
+
"AApiServer",
|
|
198
|
+
"AApiAgent",
|
|
199
|
+
"AApiServicesProvider",
|
|
200
|
+
"IApiServer",
|
|
201
|
+
"IAuthProvider",
|
|
202
|
+
"IStorageProvider",
|
|
203
|
+
"IPaymentProvider",
|
|
204
|
+
"IApiServicesProvider",
|
|
205
|
+
# Agent system
|
|
206
|
+
"XWApiAgent",
|
|
207
|
+
"IApiAgent",
|
|
208
|
+
# Engine system (engine-agnostic: engines imported from their modules)
|
|
209
|
+
"IApiServerEngine",
|
|
210
|
+
"AApiServerEngineBase",
|
|
211
|
+
"ApiServerEngineRegistry",
|
|
212
|
+
"api_server_engine_registry",
|
|
213
|
+
# Query - use XWQuery directly
|
|
214
|
+
# Action - use XWAction engines directly
|
|
215
|
+
# Middleware
|
|
216
|
+
"trace_middleware",
|
|
217
|
+
"tenant_middleware",
|
|
218
|
+
"rate_limit_middleware",
|
|
219
|
+
"auth_middleware",
|
|
220
|
+
"observability_middleware",
|
|
221
|
+
# OpenAPI
|
|
222
|
+
"merge_openapi_schemas",
|
|
223
|
+
"validate_openapi_schema",
|
|
224
|
+
"export_openapi_schema",
|
|
225
|
+
"add_openapi_metadata",
|
|
226
|
+
"get_openapi_version",
|
|
227
|
+
]
|
exonware/xwapi/action.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#exonware/xwapi/action.py
|
|
2
|
+
"""
|
|
3
|
+
xwapi action helpers: register_action_endpoint, create_action_context_dependency.
|
|
4
|
+
Thin wrapper over xwaction FastAPIActionEngine. GUIDE_TEST root-cause fix.
|
|
5
|
+
Company: eXonware.com
|
|
6
|
+
Author: eXonware Backend Team
|
|
7
|
+
Email: connect@exonware.com
|
|
8
|
+
Version: 0.9.0.2
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def register_action_endpoint(
|
|
16
|
+
app: Any,
|
|
17
|
+
action: Any,
|
|
18
|
+
*,
|
|
19
|
+
path: str | None = None,
|
|
20
|
+
method: str = "POST",
|
|
21
|
+
) -> bool:
|
|
22
|
+
"""Register an XWAction as a FastAPI endpoint. Uses xwaction FastAPIActionEngine."""
|
|
23
|
+
try:
|
|
24
|
+
from exonware.xwaction.engines import action_engine_registry
|
|
25
|
+
from exonware.xwaction.engines.fastapi import FastAPIActionEngine
|
|
26
|
+
except ImportError:
|
|
27
|
+
return False
|
|
28
|
+
engine = action_engine_registry.get_engine("fastapi")
|
|
29
|
+
if not engine:
|
|
30
|
+
engine = FastAPIActionEngine()
|
|
31
|
+
action_engine_registry.register(engine)
|
|
32
|
+
return engine.register_action(action, app, path=path or f"/{getattr(action, 'api_name', 'unknown')}", method=method)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_action_context_dependency(request: Any) -> Any:
|
|
36
|
+
"""FastAPI Depends(): build ActionContext from Request (trace_id, path, method)."""
|
|
37
|
+
from exonware.xwaction.context import ActionContext
|
|
38
|
+
trace_id = getattr(getattr(request, "state", None), "trace_id", None)
|
|
39
|
+
path = getattr(getattr(request, "url", None), "path", "unknown")
|
|
40
|
+
method = getattr(request, "method", "GET")
|
|
41
|
+
return ActionContext(
|
|
42
|
+
actor="request",
|
|
43
|
+
source=path,
|
|
44
|
+
trace_id=trace_id,
|
|
45
|
+
metadata={"method": method, "path": path},
|
|
46
|
+
)
|
exonware/xwapi/base.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
#exonware/xwapi/src/exonware/xwapi/base.py
|
|
2
|
+
"""
|
|
3
|
+
Abstract base classes for xwapi library.
|
|
4
|
+
Company: eXonware.com
|
|
5
|
+
Author: eXonware Backend Team
|
|
6
|
+
Email: connect@exonware.com
|
|
7
|
+
Version: 0.9.0.2
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from exonware.xwaction.base import AActionsProvider
|
|
14
|
+
from exonware.xwapi.contracts import (
|
|
15
|
+
IAPIEndpoint,
|
|
16
|
+
IAPIGenerator,
|
|
17
|
+
IOAuth2Provider,
|
|
18
|
+
IApiServer,
|
|
19
|
+
IApiAgent,
|
|
20
|
+
IApiServicesProvider,
|
|
21
|
+
HTTPMethod,
|
|
22
|
+
SecurityType,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AApiServicesProvider(IApiServicesProvider, AActionsProvider, ABC):
|
|
27
|
+
"""
|
|
28
|
+
Abstract base for API-level providers that expose @XWAction commands.
|
|
29
|
+
Extends AActionsProvider (xwaction); used by command providers (xwbots).
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AAPIEndpoint(ABC, IAPIEndpoint):
|
|
36
|
+
"""Abstract base class for API endpoint configuration implementing IAPIEndpoint interface."""
|
|
37
|
+
|
|
38
|
+
def __init__(
|
|
39
|
+
self,
|
|
40
|
+
endpoint_id: str,
|
|
41
|
+
path: str,
|
|
42
|
+
method: HTTPMethod,
|
|
43
|
+
entity_type: str,
|
|
44
|
+
action_name: Optional[str] = None,
|
|
45
|
+
):
|
|
46
|
+
self.endpoint_id = endpoint_id
|
|
47
|
+
self.path = path
|
|
48
|
+
self.method = method
|
|
49
|
+
self.entity_type = entity_type
|
|
50
|
+
self.action_name = action_name
|
|
51
|
+
|
|
52
|
+
def get_path(self) -> str:
|
|
53
|
+
"""Get endpoint path."""
|
|
54
|
+
return self.path
|
|
55
|
+
|
|
56
|
+
def get_method(self) -> HTTPMethod:
|
|
57
|
+
"""Get HTTP method."""
|
|
58
|
+
return self.method
|
|
59
|
+
|
|
60
|
+
def get_entity_type(self) -> str:
|
|
61
|
+
"""Get entity type name."""
|
|
62
|
+
return self.entity_type
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class AAPIGenerator(ABC, IAPIGenerator):
|
|
66
|
+
"""Abstract base class for API generator implementing IAPIGenerator interface."""
|
|
67
|
+
@abstractmethod
|
|
68
|
+
|
|
69
|
+
async def generate_openapi(self) -> dict[str, Any]:
|
|
70
|
+
"""Generate OpenAPI specification."""
|
|
71
|
+
pass
|
|
72
|
+
@abstractmethod
|
|
73
|
+
|
|
74
|
+
def create_app(self):
|
|
75
|
+
"""Create application instance (engine-specific)."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class AOAuth2Provider(ABC, IOAuth2Provider):
|
|
80
|
+
"""Abstract base class for OAuth 2.0 provider implementing IOAuth2Provider interface."""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
provider_name: str,
|
|
85
|
+
authorization_url: str,
|
|
86
|
+
token_url: str,
|
|
87
|
+
userinfo_url: Optional[str] = None,
|
|
88
|
+
):
|
|
89
|
+
self.provider_name = provider_name
|
|
90
|
+
self.authorization_url = authorization_url
|
|
91
|
+
self.token_url = token_url
|
|
92
|
+
self.userinfo_url = userinfo_url
|
|
93
|
+
|
|
94
|
+
def get_authorization_url(self) -> str:
|
|
95
|
+
"""Get authorization URL."""
|
|
96
|
+
return self.authorization_url
|
|
97
|
+
|
|
98
|
+
def get_token_url(self) -> str:
|
|
99
|
+
"""Get token URL."""
|
|
100
|
+
return self.token_url
|
|
101
|
+
|
|
102
|
+
def get_userinfo_url(self) -> Optional[str]:
|
|
103
|
+
"""Get userinfo URL."""
|
|
104
|
+
return self.userinfo_url
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class AApiServer(ABC, IApiServer):
|
|
108
|
+
"""Abstract base class for API server implementing IApiServer interface."""
|
|
109
|
+
|
|
110
|
+
def __init__(self):
|
|
111
|
+
"""Initialize AApiServer with logging."""
|
|
112
|
+
from exonware.xwsystem import get_logger
|
|
113
|
+
self.logger = get_logger(self.__class__.__name__)
|
|
114
|
+
self._is_running = False
|
|
115
|
+
self._start_time: Optional[datetime] = None
|
|
116
|
+
self._services_running = False
|
|
117
|
+
# Track flushable handlers for shutdown
|
|
118
|
+
self._flushable_handlers: list[Any] = []
|
|
119
|
+
# Call on_init hook
|
|
120
|
+
self.on_init()
|
|
121
|
+
|
|
122
|
+
def on_init(self) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Lifecycle hook: Called during initialization.
|
|
125
|
+
Subclasses can override this to perform initialization tasks.
|
|
126
|
+
This is called automatically in __init__.
|
|
127
|
+
"""
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
def pre_server_start(self) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Lifecycle hook: Called before server starts (protocol-agnostic).
|
|
133
|
+
For HTTP engines, this is equivalent to pre_http_start().
|
|
134
|
+
For other protocols (gRPC, WebSocket, etc.), this is the generic hook.
|
|
135
|
+
Subclasses can override this to perform tasks before the server
|
|
136
|
+
starts (e.g., validate configuration, check dependencies).
|
|
137
|
+
"""
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
def post_server_start(self) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Lifecycle hook: Called after server starts (protocol-agnostic).
|
|
143
|
+
For HTTP engines, this is equivalent to post_http_start().
|
|
144
|
+
For other protocols (gRPC, WebSocket, etc.), this is the generic hook.
|
|
145
|
+
Subclasses can override this to perform tasks after the server
|
|
146
|
+
has started (e.g., register routes, warm up caches).
|
|
147
|
+
"""
|
|
148
|
+
pass
|
|
149
|
+
|
|
150
|
+
def pre_http_start(self) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Lifecycle hook: Called before HTTP server starts (HTTP-specific).
|
|
153
|
+
For backward compatibility. Delegates to pre_server_start().
|
|
154
|
+
Subclasses can override this for HTTP-specific logic.
|
|
155
|
+
"""
|
|
156
|
+
self.pre_server_start()
|
|
157
|
+
|
|
158
|
+
def post_http_start(self) -> None:
|
|
159
|
+
"""
|
|
160
|
+
Lifecycle hook: Called after HTTP server starts (HTTP-specific).
|
|
161
|
+
For backward compatibility. Delegates to post_server_start().
|
|
162
|
+
Subclasses can override this for HTTP-specific logic.
|
|
163
|
+
"""
|
|
164
|
+
self.post_server_start()
|
|
165
|
+
|
|
166
|
+
def pre_services_start(self) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Lifecycle hook: Called before domain services start.
|
|
169
|
+
Subclasses can override this to perform tasks before domain services
|
|
170
|
+
start (e.g., load data, initialize connections).
|
|
171
|
+
"""
|
|
172
|
+
pass
|
|
173
|
+
|
|
174
|
+
def post_services_start(self) -> None:
|
|
175
|
+
"""
|
|
176
|
+
Lifecycle hook: Called after domain services start.
|
|
177
|
+
Subclasses can override this to perform tasks after domain services
|
|
178
|
+
have started (e.g., verify services are ready, start background tasks).
|
|
179
|
+
"""
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
def pre_stop(self) -> None:
|
|
183
|
+
"""
|
|
184
|
+
Lifecycle hook: Called before stopping.
|
|
185
|
+
Subclasses can override this to perform cleanup tasks before stopping
|
|
186
|
+
(e.g., save state, notify clients).
|
|
187
|
+
"""
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def post_stop(self) -> None:
|
|
191
|
+
"""
|
|
192
|
+
Lifecycle hook: Called after stopping.
|
|
193
|
+
Subclasses can override this to perform final cleanup tasks after
|
|
194
|
+
stopping (e.g., close connections, cleanup resources).
|
|
195
|
+
"""
|
|
196
|
+
pass
|
|
197
|
+
|
|
198
|
+
def register_flushable(self, handler: Any) -> None:
|
|
199
|
+
"""
|
|
200
|
+
Register a flushable handler for shutdown cleanup.
|
|
201
|
+
Handlers must implement flush() and optionally close() methods.
|
|
202
|
+
These will be called during shutdown to ensure all logs/data are flushed.
|
|
203
|
+
Args:
|
|
204
|
+
handler: Object with flush() and optionally close() methods
|
|
205
|
+
"""
|
|
206
|
+
if handler not in self._flushable_handlers:
|
|
207
|
+
self._flushable_handlers.append(handler)
|
|
208
|
+
self.logger.debug(f"Registered flushable handler: {type(handler).__name__}")
|
|
209
|
+
|
|
210
|
+
def unregister_flushable(self, handler: Any) -> None:
|
|
211
|
+
"""
|
|
212
|
+
Unregister a flushable handler.
|
|
213
|
+
Args:
|
|
214
|
+
handler: Handler to unregister
|
|
215
|
+
"""
|
|
216
|
+
if handler in self._flushable_handlers:
|
|
217
|
+
self._flushable_handlers.remove(handler)
|
|
218
|
+
self.logger.debug(f"Unregistered flushable handler: {type(handler).__name__}")
|
|
219
|
+
@abstractmethod
|
|
220
|
+
|
|
221
|
+
def start(self, host: str = "0.0.0.0", port: int = 8000, **kwargs) -> None:
|
|
222
|
+
"""Start the API server services."""
|
|
223
|
+
pass
|
|
224
|
+
@abstractmethod
|
|
225
|
+
|
|
226
|
+
def stop(self) -> None:
|
|
227
|
+
"""Stop the API server services."""
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
def restart(self, host: str = "0.0.0.0", port: int = 8000, **kwargs) -> None:
|
|
231
|
+
"""Restart the API server."""
|
|
232
|
+
self.logger.info("Restarting server...")
|
|
233
|
+
self.stop()
|
|
234
|
+
self.start(host=host, port=port, **kwargs)
|
|
235
|
+
self.logger.info("Server restarted")
|
|
236
|
+
|
|
237
|
+
def status(self) -> dict[str, Any]:
|
|
238
|
+
"""
|
|
239
|
+
Get server status.
|
|
240
|
+
Returns:
|
|
241
|
+
Dictionary with server status information
|
|
242
|
+
"""
|
|
243
|
+
uptime = None
|
|
244
|
+
if self._start_time:
|
|
245
|
+
from datetime import datetime
|
|
246
|
+
uptime = (datetime.now() - self._start_time).total_seconds()
|
|
247
|
+
return {
|
|
248
|
+
"status": "running" if self._is_running else "stopped",
|
|
249
|
+
"is_running": self._is_running,
|
|
250
|
+
"services_running": getattr(self, '_services_running', False),
|
|
251
|
+
"start_time": self._start_time.isoformat() if self._start_time else None,
|
|
252
|
+
"uptime_seconds": uptime,
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
def health(self) -> dict[str, Any]:
|
|
256
|
+
"""
|
|
257
|
+
Get server health check.
|
|
258
|
+
Returns:
|
|
259
|
+
Dictionary with health check information
|
|
260
|
+
"""
|
|
261
|
+
from datetime import datetime
|
|
262
|
+
return {
|
|
263
|
+
"status": "healthy" if self._is_running else "unhealthy",
|
|
264
|
+
"timestamp": datetime.now().isoformat(),
|
|
265
|
+
"is_running": self._is_running,
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
def log(self, level: str = "INFO", message: str = "") -> None:
|
|
269
|
+
"""
|
|
270
|
+
Log a message.
|
|
271
|
+
Args:
|
|
272
|
+
level: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)
|
|
273
|
+
message: Log message
|
|
274
|
+
"""
|
|
275
|
+
log_method = getattr(self.logger, level.lower(), self.logger.info)
|
|
276
|
+
log_method(message)
|
|
277
|
+
@property
|
|
278
|
+
|
|
279
|
+
def is_running(self) -> bool:
|
|
280
|
+
"""Check if server is running."""
|
|
281
|
+
return self._is_running
|
|
282
|
+
|
|
283
|
+
def pre_register_action(self, action: Any) -> Any:
|
|
284
|
+
"""
|
|
285
|
+
Lifecycle hook: Called before registering an action.
|
|
286
|
+
Subclasses can override this to transform actions before registration
|
|
287
|
+
(e.g., inject authorizers, add middleware, modify metadata).
|
|
288
|
+
Args:
|
|
289
|
+
action: Action to be registered
|
|
290
|
+
Returns:
|
|
291
|
+
Transformed action (or original if no transformation needed)
|
|
292
|
+
"""
|
|
293
|
+
return action
|
|
294
|
+
|
|
295
|
+
def post_register_action(self, action: Any, route_info: dict[str, Any]) -> None:
|
|
296
|
+
"""
|
|
297
|
+
Lifecycle hook: Called after registering an action.
|
|
298
|
+
Subclasses can override this to perform tasks after action registration
|
|
299
|
+
(e.g., log registration, update metrics, validate route).
|
|
300
|
+
Args:
|
|
301
|
+
action: Registered action
|
|
302
|
+
route_info: Dictionary with route information (path, method, etc.)
|
|
303
|
+
"""
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class AApiAgent(ABC, IApiAgent):
|
|
308
|
+
"""Abstract base class for API agent implementing IApiAgent interface."""
|
|
309
|
+
|
|
310
|
+
def __init__(self, name: Optional[str] = None):
|
|
311
|
+
"""
|
|
312
|
+
Initialize API agent.
|
|
313
|
+
Args:
|
|
314
|
+
name: Optional name for this agent
|
|
315
|
+
"""
|
|
316
|
+
self._name = name or self.__class__.__name__
|
|
317
|
+
self._actions: list[Any] = []
|
|
318
|
+
@property
|
|
319
|
+
|
|
320
|
+
def name(self) -> str:
|
|
321
|
+
"""Get agent name."""
|
|
322
|
+
return self._name
|
|
323
|
+
@abstractmethod
|
|
324
|
+
|
|
325
|
+
def get_actions(self) -> list[Any]:
|
|
326
|
+
"""
|
|
327
|
+
Get list of XWAction instances provided by this agent.
|
|
328
|
+
Subclasses must implement this to return their actions.
|
|
329
|
+
Returns:
|
|
330
|
+
List of XWAction instances or decorated functions
|
|
331
|
+
"""
|
|
332
|
+
pass
|
|
333
|
+
|
|
334
|
+
def register_to_server(self, server: Any, path_prefix: str = "") -> int:
|
|
335
|
+
"""
|
|
336
|
+
Register all actions from this agent to an ApiServer.
|
|
337
|
+
Args:
|
|
338
|
+
server: ApiServer instance to register actions to
|
|
339
|
+
path_prefix: Optional path prefix for all actions
|
|
340
|
+
Returns:
|
|
341
|
+
Number of successfully registered actions
|
|
342
|
+
"""
|
|
343
|
+
actions = self.get_actions()
|
|
344
|
+
if not actions:
|
|
345
|
+
return 0
|
|
346
|
+
# Use server's register_actions method
|
|
347
|
+
if hasattr(server, 'register_actions'):
|
|
348
|
+
return server.register_actions(actions, path_prefix=path_prefix)
|
|
349
|
+
elif hasattr(server, 'register_action'):
|
|
350
|
+
# Fallback: register one by one
|
|
351
|
+
registered = 0
|
|
352
|
+
for action in actions:
|
|
353
|
+
if server.register_action(action, path=path_prefix):
|
|
354
|
+
registered += 1
|
|
355
|
+
return registered
|
|
356
|
+
else:
|
|
357
|
+
raise TypeError(f"Server must have register_actions or register_action method, got {type(server)}")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#exonware/xwapi/src/exonware/xwapi/client/__init__.py
|
|
2
|
+
"""
|
|
3
|
+
Client package for xwapi library.
|
|
4
|
+
Contains all client/agent-related functionality:
|
|
5
|
+
- XWApiAgent: Main agent implementation
|
|
6
|
+
- engines/: Client engine implementations
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from exonware.xwapi.client.xwclient import XWApiAgent
|
|
10
|
+
__all__ = [
|
|
11
|
+
"XWApiAgent",
|
|
12
|
+
]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#exonware/xwapi/src/exonware/xwapi/client/base.py
|
|
2
|
+
"""
|
|
3
|
+
Abstract base classes for xwapi client library.
|
|
4
|
+
This module re-exports AApiAgent from exonware.xwapi.base for backward compatibility.
|
|
5
|
+
All new code should import from exonware.xwapi.base directly.
|
|
6
|
+
Company: eXonware.com
|
|
7
|
+
Author: eXonware Backend Team
|
|
8
|
+
Email: connect@exonware.com
|
|
9
|
+
Version: 0.9.0.2
|
|
10
|
+
"""
|
|
11
|
+
# Re-export AApiAgent from xwapi.base for backward compatibility
|
|
12
|
+
|
|
13
|
+
from exonware.xwapi.base import AApiAgent
|
|
14
|
+
__all__ = ['AApiAgent']
|
|
15
|
+
# Original implementation moved to exonware.xwapi.base for consistency
|
|
16
|
+
# This module kept for backward compatibility with existing imports
|