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.
Files changed (73) hide show
  1. exonware/__init__.py +1 -0
  2. exonware/xwapi/__init__.py +227 -0
  3. exonware/xwapi/action.py +46 -0
  4. exonware/xwapi/base.py +357 -0
  5. exonware/xwapi/client/__init__.py +12 -0
  6. exonware/xwapi/client/base.py +16 -0
  7. exonware/xwapi/client/engines/__init__.py +97 -0
  8. exonware/xwapi/client/engines/base.py +47 -0
  9. exonware/xwapi/client/engines/contracts.py +142 -0
  10. exonware/xwapi/client/engines/native.py +118 -0
  11. exonware/xwapi/client/xwclient.py +674 -0
  12. exonware/xwapi/common/__init__.py +91 -0
  13. exonware/xwapi/common/app.py +183 -0
  14. exonware/xwapi/common/openapi.py +196 -0
  15. exonware/xwapi/common/serialization.py +58 -0
  16. exonware/xwapi/common/utils/__init__.py +11 -0
  17. exonware/xwapi/common/utils/async_utils.py +99 -0
  18. exonware/xwapi/config.py +43 -0
  19. exonware/xwapi/contracts.py +206 -0
  20. exonware/xwapi/defs.py +41 -0
  21. exonware/xwapi/entity_store.py +109 -0
  22. exonware/xwapi/errors.py +371 -0
  23. exonware/xwapi/facade.py +551 -0
  24. exonware/xwapi/providers.py +240 -0
  25. exonware/xwapi/query.py +87 -0
  26. exonware/xwapi/schema/__init__.py +25 -0
  27. exonware/xwapi/schema/contracts.py +59 -0
  28. exonware/xwapi/schema/generator.py +57 -0
  29. exonware/xwapi/schema/graphql.py +67 -0
  30. exonware/xwapi/schema/validator.py +69 -0
  31. exonware/xwapi/serialization.py +22 -0
  32. exonware/xwapi/server/__init__.py +21 -0
  33. exonware/xwapi/server/admin/__init__.py +13 -0
  34. exonware/xwapi/server/admin/router.py +459 -0
  35. exonware/xwapi/server/base.py +16 -0
  36. exonware/xwapi/server/engines/__init__.py +199 -0
  37. exonware/xwapi/server/engines/base.py +94 -0
  38. exonware/xwapi/server/engines/contracts.py +214 -0
  39. exonware/xwapi/server/engines/email_store.py +220 -0
  40. exonware/xwapi/server/engines/fastapi.py +473 -0
  41. exonware/xwapi/server/engines/flask.py +338 -0
  42. exonware/xwapi/server/engines/graphql.py +319 -0
  43. exonware/xwapi/server/engines/grpc.py +329 -0
  44. exonware/xwapi/server/engines/http_base.py +156 -0
  45. exonware/xwapi/server/engines/imap.py +303 -0
  46. exonware/xwapi/server/engines/pop3.py +270 -0
  47. exonware/xwapi/server/engines/smtp.py +192 -0
  48. exonware/xwapi/server/engines/websocket.py +215 -0
  49. exonware/xwapi/server/governance/__init__.py +19 -0
  50. exonware/xwapi/server/governance/lockfile.py +196 -0
  51. exonware/xwapi/server/governance/registry.py +145 -0
  52. exonware/xwapi/server/http/__init__.py +8 -0
  53. exonware/xwapi/server/http/error_adapter.py +32 -0
  54. exonware/xwapi/server/middleware/__init__.py +28 -0
  55. exonware/xwapi/server/middleware/admin_auth.py +69 -0
  56. exonware/xwapi/server/middleware/api_token.py +210 -0
  57. exonware/xwapi/server/middleware/auth.py +149 -0
  58. exonware/xwapi/server/middleware/observability.py +100 -0
  59. exonware/xwapi/server/middleware/pause.py +47 -0
  60. exonware/xwapi/server/middleware/ratelimit.py +140 -0
  61. exonware/xwapi/server/middleware/tenant.py +70 -0
  62. exonware/xwapi/server/middleware/trace.py +56 -0
  63. exonware/xwapi/server/pipeline/__init__.py +18 -0
  64. exonware/xwapi/server/pipeline/manager.py +57 -0
  65. exonware/xwapi/server/pipeline/outbox.py +212 -0
  66. exonware/xwapi/server/pipeline/worker.py +126 -0
  67. exonware/xwapi/server/xwserver.py +1773 -0
  68. exonware/xwapi/token_management.py +245 -0
  69. exonware/xwapi/version.py +82 -0
  70. exonware_xwapi-0.9.0.2.dist-info/METADATA +176 -0
  71. exonware_xwapi-0.9.0.2.dist-info/RECORD +73 -0
  72. exonware_xwapi-0.9.0.2.dist-info/WHEEL +4 -0
  73. 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
+ ]
@@ -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