servicekit 0.8.2__tar.gz → 0.9.0__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.
- {servicekit-0.8.2 → servicekit-0.9.0}/PKG-INFO +1 -1
- {servicekit-0.8.2 → servicekit-0.9.0}/pyproject.toml +1 -1
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/registration.py +89 -3
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/service_builder.py +22 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/README.md +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/.DS_Store +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/__init__.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/.DS_Store +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/__init__.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/app.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/apps/.DS_Store +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/apps/landing/index.html +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/apps/landing/manifest.json +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/auth.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/crud.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/dependencies.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/middleware.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/monitoring.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/pagination.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/router.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/routers/__init__.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/routers/health.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/routers/job.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/routers/metrics.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/routers/system.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/sse.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/api/utilities.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/database.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/exceptions.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/gunicorn.conf.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/logging.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/manager.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/models.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/py.typed +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/repository.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/scheduler.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/schemas.py +0 -0
- {servicekit-0.8.2 → servicekit-0.9.0}/src/servicekit/types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: servicekit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Async SQLAlchemy framework with FastAPI integration - reusable foundation for building data services
|
|
5
5
|
Keywords: fastapi,sqlalchemy,async,database,rest-api,crud,framework
|
|
6
6
|
Author: Morten Hansen
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "servicekit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.9.0"
|
|
4
4
|
description = "Async SQLAlchemy framework with FastAPI integration - reusable foundation for building data services"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "Morten Hansen", email = "morten@winterop.com" }]
|
|
@@ -6,7 +6,7 @@ import socket
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
import httpx
|
|
9
|
-
from pydantic import BaseModel
|
|
9
|
+
from pydantic import BaseModel, ConfigDict
|
|
10
10
|
|
|
11
11
|
from servicekit.logging import get_logger
|
|
12
12
|
|
|
@@ -19,6 +19,26 @@ _ping_url: str | None = None
|
|
|
19
19
|
_service_key: str | None = None
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
class RegistrationConfig(BaseModel):
|
|
23
|
+
"""Parameters for service registration, used for re-registration in keepalive."""
|
|
24
|
+
|
|
25
|
+
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
26
|
+
|
|
27
|
+
orchestrator_url: str | None = None
|
|
28
|
+
host: str | None = None
|
|
29
|
+
port: int | None = None
|
|
30
|
+
info: BaseModel
|
|
31
|
+
orchestrator_url_env: str = "SERVICEKIT_ORCHESTRATOR_URL"
|
|
32
|
+
host_env: str = "SERVICEKIT_HOST"
|
|
33
|
+
port_env: str = "SERVICEKIT_PORT"
|
|
34
|
+
max_retries: int = 5
|
|
35
|
+
retry_delay: float = 2.0
|
|
36
|
+
fail_on_error: bool = False
|
|
37
|
+
timeout: float = 10.0
|
|
38
|
+
service_key: str | None = None
|
|
39
|
+
service_key_env: str = "SERVICEKIT_REGISTRATION_KEY"
|
|
40
|
+
|
|
41
|
+
|
|
22
42
|
def _resolve_service_key(service_key: str | None, service_key_env: str) -> str | None:
|
|
23
43
|
"""Resolve service key from parameter or environment variable."""
|
|
24
44
|
if service_key:
|
|
@@ -218,7 +238,14 @@ async def register_service(
|
|
|
218
238
|
return None
|
|
219
239
|
|
|
220
240
|
|
|
221
|
-
async def _keepalive_loop(
|
|
241
|
+
async def _keepalive_loop(
|
|
242
|
+
ping_url: str,
|
|
243
|
+
interval: float,
|
|
244
|
+
timeout: float,
|
|
245
|
+
service_key: str | None,
|
|
246
|
+
registration_config: RegistrationConfig | None = None,
|
|
247
|
+
re_register_grace_period: float = 30.0,
|
|
248
|
+
) -> None:
|
|
222
249
|
"""Background task to periodically ping the orchestrator."""
|
|
223
250
|
logger.info("keepalive.started", ping_url=ping_url, interval_seconds=interval)
|
|
224
251
|
|
|
@@ -247,6 +274,54 @@ async def _keepalive_loop(ping_url: str, interval: float, timeout: float, servic
|
|
|
247
274
|
logger.info("keepalive.cancelled", service_id=_service_id)
|
|
248
275
|
raise
|
|
249
276
|
|
|
277
|
+
except httpx.HTTPStatusError as e:
|
|
278
|
+
if e.response.status_code == 404 and registration_config is not None:
|
|
279
|
+
logger.warning(
|
|
280
|
+
"keepalive.service_not_found",
|
|
281
|
+
service_id=_service_id,
|
|
282
|
+
ping_url=ping_url,
|
|
283
|
+
grace_period=re_register_grace_period,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Grace period to avoid thundering herd during orchestrator flap
|
|
287
|
+
await asyncio.sleep(re_register_grace_period)
|
|
288
|
+
|
|
289
|
+
# Attempt re-registration with original parameters
|
|
290
|
+
try:
|
|
291
|
+
result = await register_service(**registration_config.model_dump())
|
|
292
|
+
if result and result.get("ping_url"):
|
|
293
|
+
ping_url = result["ping_url"]
|
|
294
|
+
# Rebuild headers in case service_key changed
|
|
295
|
+
headers = {}
|
|
296
|
+
if _service_key:
|
|
297
|
+
headers["X-Service-Key"] = _service_key
|
|
298
|
+
logger.info(
|
|
299
|
+
"keepalive.re_registered",
|
|
300
|
+
service_id=_service_id,
|
|
301
|
+
new_ping_url=ping_url,
|
|
302
|
+
)
|
|
303
|
+
else:
|
|
304
|
+
logger.warning(
|
|
305
|
+
"keepalive.re_registration_failed",
|
|
306
|
+
service_id=_service_id,
|
|
307
|
+
reason="register_service returned None",
|
|
308
|
+
)
|
|
309
|
+
except Exception as re_reg_error:
|
|
310
|
+
logger.warning(
|
|
311
|
+
"keepalive.re_registration_error",
|
|
312
|
+
service_id=_service_id,
|
|
313
|
+
error=str(re_reg_error),
|
|
314
|
+
error_type=type(re_reg_error).__name__,
|
|
315
|
+
)
|
|
316
|
+
else:
|
|
317
|
+
logger.warning(
|
|
318
|
+
"keepalive.ping_failed",
|
|
319
|
+
service_id=_service_id,
|
|
320
|
+
ping_url=ping_url,
|
|
321
|
+
error=str(e),
|
|
322
|
+
error_type=type(e).__name__,
|
|
323
|
+
)
|
|
324
|
+
|
|
250
325
|
except Exception as e:
|
|
251
326
|
logger.warning(
|
|
252
327
|
"keepalive.ping_failed",
|
|
@@ -264,6 +339,8 @@ async def start_keepalive(
|
|
|
264
339
|
timeout: float = 10.0,
|
|
265
340
|
service_key: str | None = None,
|
|
266
341
|
service_key_env: str = "SERVICEKIT_REGISTRATION_KEY",
|
|
342
|
+
registration_config: RegistrationConfig | None = None,
|
|
343
|
+
re_register_grace_period: float = 30.0,
|
|
267
344
|
) -> None:
|
|
268
345
|
"""Start background keepalive task to ping orchestrator."""
|
|
269
346
|
global _keepalive_task
|
|
@@ -275,7 +352,16 @@ async def start_keepalive(
|
|
|
275
352
|
# Resolve service key (use global from registration if not provided)
|
|
276
353
|
resolved_service_key = _resolve_service_key(service_key, service_key_env) or _service_key
|
|
277
354
|
|
|
278
|
-
_keepalive_task = asyncio.create_task(
|
|
355
|
+
_keepalive_task = asyncio.create_task(
|
|
356
|
+
_keepalive_loop(
|
|
357
|
+
ping_url,
|
|
358
|
+
interval,
|
|
359
|
+
timeout,
|
|
360
|
+
resolved_service_key,
|
|
361
|
+
registration_config=registration_config,
|
|
362
|
+
re_register_grace_period=re_register_grace_period,
|
|
363
|
+
)
|
|
364
|
+
)
|
|
279
365
|
logger.info("keepalive.task_started", ping_url=ping_url, interval_seconds=interval)
|
|
280
366
|
|
|
281
367
|
|
|
@@ -95,6 +95,7 @@ class _RegistrationOptions:
|
|
|
95
95
|
auto_deregister: bool
|
|
96
96
|
service_key: str | None
|
|
97
97
|
service_key_env: str
|
|
98
|
+
re_register_grace_period: float
|
|
98
99
|
|
|
99
100
|
|
|
100
101
|
class ServiceInfo(BaseModel):
|
|
@@ -330,6 +331,7 @@ class BaseServiceBuilder:
|
|
|
330
331
|
auto_deregister: bool = True,
|
|
331
332
|
service_key: str | None = None,
|
|
332
333
|
service_key_env: str = "SERVICEKIT_REGISTRATION_KEY",
|
|
334
|
+
re_register_grace_period: float = 30.0,
|
|
333
335
|
) -> Self:
|
|
334
336
|
"""Enable service registration with orchestrator for service discovery."""
|
|
335
337
|
self._registration_options = _RegistrationOptions(
|
|
@@ -348,6 +350,7 @@ class BaseServiceBuilder:
|
|
|
348
350
|
auto_deregister=auto_deregister,
|
|
349
351
|
service_key=service_key,
|
|
350
352
|
service_key_env=service_key_env,
|
|
353
|
+
re_register_grace_period=re_register_grace_period,
|
|
351
354
|
)
|
|
352
355
|
return self
|
|
353
356
|
|
|
@@ -685,12 +688,31 @@ class BaseServiceBuilder:
|
|
|
685
688
|
if registration_info and registration_options.enable_keepalive:
|
|
686
689
|
ping_url = registration_info.get("ping_url")
|
|
687
690
|
if ping_url:
|
|
691
|
+
from .registration import RegistrationConfig
|
|
692
|
+
|
|
693
|
+
registration_config = RegistrationConfig(
|
|
694
|
+
orchestrator_url=registration_options.orchestrator_url,
|
|
695
|
+
host=registration_options.host,
|
|
696
|
+
port=registration_options.port,
|
|
697
|
+
info=info,
|
|
698
|
+
orchestrator_url_env=registration_options.orchestrator_url_env,
|
|
699
|
+
host_env=registration_options.host_env,
|
|
700
|
+
port_env=registration_options.port_env,
|
|
701
|
+
max_retries=registration_options.max_retries,
|
|
702
|
+
retry_delay=registration_options.retry_delay,
|
|
703
|
+
fail_on_error=False,
|
|
704
|
+
timeout=registration_options.timeout,
|
|
705
|
+
service_key=registration_options.service_key,
|
|
706
|
+
service_key_env=registration_options.service_key_env,
|
|
707
|
+
)
|
|
688
708
|
await start_keepalive(
|
|
689
709
|
ping_url=ping_url,
|
|
690
710
|
interval=registration_options.keepalive_interval,
|
|
691
711
|
timeout=registration_options.timeout,
|
|
692
712
|
service_key=registration_options.service_key,
|
|
693
713
|
service_key_env=registration_options.service_key_env,
|
|
714
|
+
registration_config=registration_config,
|
|
715
|
+
re_register_grace_period=registration_options.re_register_grace_period,
|
|
694
716
|
)
|
|
695
717
|
|
|
696
718
|
try:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|