genropy-asgi 0.2.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.
@@ -0,0 +1,19 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """genropy-asgi — the bridge between genro-asgi and legacy GenroPy.
5
+
6
+ The generic commander/worker model lives in genro-asgi core
7
+ (``genro_asgi.applications.multi_worker_application``); this package is the
8
+ GenroPy-specific bridge on top of it. Three submodules, the only ``gnr.*``-aware code:
9
+
10
+ - ``genropy_asgi.spa`` — the SPA bridge: ``GenropySpaApplication`` (single) and
11
+ ``GenropyWorkerApplication`` (pool child) host a legacy ``GnrWsgiSite``, plus the
12
+ ``gnrasgiserve`` CLI and the in-process register client.
13
+ - ``genropy_asgi.proxy`` — the OpenAPI bridge: a ``GnrApp`` behind an
14
+ ``OpenApiApplication`` with thread-local db cleanup.
15
+ - ``genropy_asgi.siteregister`` — the daemonless register the legacy imports as
16
+ ``gnr.web.daemon`` (entry-point ``gnr.web:daemon``), replacing the register daemon.
17
+ """
18
+
19
+ __version__ = "0.2.0"
@@ -0,0 +1,17 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """genropy-proxy — GenroPy legacy db behind an OpenApiApplication (collaudo).
5
+
6
+ ``GenropyProxyMixin`` hosts a ``GnrApp`` and closes its thread-local db
7
+ connection via the ``route_cleanup`` hook (run in the executor thread by
8
+ genro-asgi's ``make_callable``). ``GenropyProxyOpenApiApplication`` composes the
9
+ mixin with ``OpenApiApplication``. The only ``gnr.*``-aware piece; everything
10
+ REST/OpenAPI comes from genro-asgi.
11
+
12
+ """
13
+
14
+ from .genropy_proxy import GenropyProxyMixin, GenropyProxyOpenApiApplication
15
+
16
+ __all__ = ["GenropyProxyMixin", "GenropyProxyOpenApiApplication"]
17
+ __version__ = "0.1.0"
@@ -0,0 +1,102 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """GenropyProxyMixin — a GenroPy legacy db behind an OpenApiApplication.
5
+
6
+ An ``OpenApiApplication`` mounts a RoutingClass and exposes it as REST (and, via
7
+ ``McpOpenApiApplication``, as MCP). When those handlers need the GenroPy legacy
8
+ db, someone must own its lifecycle: the connection is thread-local and must be
9
+ closed on the thread that opened it — the executor thread where the handler ran,
10
+ not the loop. genro-asgi provides exactly that hook (``route_cleanup``, run in
11
+ the executor thread by ``make_callable``); this mixin fills it.
12
+
13
+ The mixin instantiates a ``GnrApp`` and exposes it as ``self.gnr_app`` (the only
14
+ channel the mounted RoutingClass reads, e.g. through its parent). It overrides
15
+ only what it owns — ``route_cleanup`` (close the current thread's connection) —
16
+ and never the ``OpenApiApplication`` machinery. Direction of dependency is
17
+ contrib -> genro-asgi: this imports ``gnr.*``; genro-asgi never imports GenroPy.
18
+
19
+ Compose it with the base to get a mountable app::
20
+
21
+ class GenropyProxyOpenApiApplication(GenropyProxyMixin, OpenApiApplication):
22
+ ...
23
+
24
+ NOTE (collaudo): this package currently lives in genro-asgi/contrib for
25
+ end-to-end testing against the real GenroPy legacy. Once proven it will move to
26
+ the genropy-asgi repository.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import logging
32
+ from typing import Any
33
+
34
+ from genro_asgi.applications.openapi_application import OpenApiApplication
35
+
36
+ log = logging.getLogger("genropy_asgi.proxy")
37
+
38
+ __all__ = ["GenropyProxyMixin", "GenropyProxyOpenApiApplication"]
39
+
40
+
41
+ class GenropyProxyMixin:
42
+ """Owns a ``GnrApp`` and closes its db connection in the executor thread.
43
+
44
+ Mixed before an ``OpenApiApplication``: it builds the GnrApp in ``on_init``
45
+ (before delegating to the base, which mounts the RoutingClass), exposes it as
46
+ ``gnr_app``, and fills ``route_cleanup`` to release the thread-local db
47
+ connection after each handler — where it is thread-correct.
48
+ """
49
+
50
+ def on_init(self, instance: str | None = None, debug: bool = False, **kwargs: Any) -> None:
51
+ """Build the GnrApp, then let the OpenApiApplication base mount the API.
52
+
53
+ Args:
54
+ instance: GenroPy instance name (or path) resolved by GnrApp.
55
+ debug: Passed through to GnrApp.
56
+ **kwargs: Forwarded to ``OpenApiApplication.on_init`` (routing_class,
57
+ module, docs, api_name, ...).
58
+ """
59
+ from gnr.app.gnrapp import GnrApp
60
+
61
+ if not instance:
62
+ raise ValueError("GenropyProxyMixin requires an 'instance'")
63
+ log.info("Creating GnrApp for instance '%s'", instance)
64
+ self._gnr_app = GnrApp(instance, debug=debug)
65
+ log.info("GnrApp '%s' ready", instance)
66
+ super().on_init(**kwargs) # type: ignore[misc]
67
+
68
+ @property
69
+ def gnr_app(self) -> Any:
70
+ """The hosted GnrApp — the only channel the mounted RoutingClass reads."""
71
+ return self._gnr_app
72
+
73
+ def route_cleanup(self) -> None:
74
+ """Close the current thread's db connection after the handler.
75
+
76
+ Runs in the executor thread (via ``make_callable``), which is where the
77
+ GnrApp opened its thread-local connection, so this is where it must be
78
+ closed. The request-level cleanup runs on the loop and cannot do it.
79
+ """
80
+ db = getattr(self._gnr_app, "db", None)
81
+ if db is not None:
82
+ db.closeConnection()
83
+
84
+ def on_shutdown(self) -> None:
85
+ """Release the GnrApp on server stop, then the base."""
86
+ db = getattr(self._gnr_app, "db", None)
87
+ if db is not None:
88
+ db.closeConnection()
89
+ super().on_shutdown() # type: ignore[misc]
90
+
91
+
92
+ class GenropyProxyOpenApiApplication(GenropyProxyMixin, OpenApiApplication):
93
+ """OpenApiApplication hosting a GnrApp, mountable on an AsgiServer.
94
+
95
+ MRO: the mixin owns ``on_init``/``route_cleanup``/``on_shutdown``; the base
96
+ owns the REST + OpenAPI machinery. Mount it like any OpenApiApplication and
97
+ give it an ``instance`` plus a ``routing_class`` (or ``module``).
98
+ """
99
+
100
+
101
+ if __name__ == "__main__":
102
+ pass
File without changes
genropy_asgi/py.typed ADDED
File without changes
@@ -0,0 +1,20 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """genropy_asgi.siteregister — the daemonless register the legacy imports.
5
+
6
+ This subpackage is the provider of the ``gnr.web:daemon`` entry-point: the legacy
7
+ ``gnr.web.daemon`` switcher installs it as ``gnr.web.daemon`` in ``sys.modules``, so
8
+ ``from gnr.web.daemon.siteregister_client import SiteRegisterClient`` and the handful of
9
+ other legacy imports resolve to the files here — with no register daemon behind them.
10
+
11
+ The register is served entirely in-process by ``GenropyRegisterClient`` (the SPA
12
+ application's own registries/surface/mailbox). The ``handler`` / ``service`` /
13
+ ``processes`` modules are fakes: they exist only so the ``gnr.web.daemon.*`` namespace
14
+ resolves for the daemon-CLI / ``app.gnrdaemon`` paths (never reached in the request
15
+ path), and raise if actually used — there is no daemon in this build.
16
+ """
17
+
18
+ __all__ = ["SiteRegisterClient", "GenropyRegisterClient"]
19
+
20
+ from .siteregister_client import GenropyRegisterClient, SiteRegisterClient
@@ -0,0 +1,36 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Exceptions for the in-process siteregister.
5
+
6
+ The names mirror the register-daemon hierarchy the legacy code catches (it does
7
+ ``except GnrDaemonLocked`` around a store lock). There is no daemon here, so
8
+ ``GnrDaemonLocked`` only ever fires on the in-process lock retry budget, and the
9
+ transport errors (``GnrDaemonUnavailable``, ``GnrDaemonMethodNotFound``) can never
10
+ happen — they are kept only so legacy imports and ``except`` clauses resolve.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ __all__ = [
16
+ "GnrDaemonException",
17
+ "GnrDaemonLocked",
18
+ "GnrDaemonMethodNotFound",
19
+ "GnrDaemonUnavailable",
20
+ ]
21
+
22
+
23
+ class GnrDaemonException(Exception):
24
+ """Base class for register errors (kept for legacy compatibility)."""
25
+
26
+
27
+ class GnrDaemonLocked(GnrDaemonException):
28
+ """An in-process item lock could not be acquired within the retry budget."""
29
+
30
+
31
+ class GnrDaemonMethodNotFound(GnrDaemonException):
32
+ """Never raised in-process; kept so legacy imports resolve."""
33
+
34
+
35
+ class GnrDaemonUnavailable(GnrDaemonException):
36
+ """Never raised in-process (no transport); kept for import compatibility."""
@@ -0,0 +1,28 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Fake ``gnr.web.daemon.handler`` — there is no daemon in this build.
5
+
6
+ The legacy lazily imports ``GnrDaemonProxy`` here only when something reads
7
+ ``app.gnrdaemon`` (a control/stop handle for the register daemon). Nothing in the
8
+ request path does, so this is never reached in practice; it exists so the namespace
9
+ resolves, and any actual use is an explicit error rather than a silent no-op.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+ __all__ = ["GnrDaemonProxy"]
17
+
18
+ _NO_DAEMON = "no register daemon in this build (the register is served in-process)"
19
+
20
+
21
+ class GnrDaemonProxy:
22
+ """Stub daemon-control proxy: constructing or calling it is an explicit error."""
23
+
24
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
25
+ raise RuntimeError(_NO_DAEMON)
26
+
27
+ def proxy(self) -> Any:
28
+ raise RuntimeError(_NO_DAEMON)
@@ -0,0 +1,12 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Fake ``gnr.web.daemon.processes`` — the daemon service-process machinery is gone.
5
+
6
+ No module outside the daemon package imported this at runtime; it exists only so the
7
+ ``gnr.web.daemon.processes`` namespace resolves for any stray import. Empty on purpose.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ __all__: list[str] = []
@@ -0,0 +1,28 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """Fake ``gnr.web.daemon.service`` — there is no daemon service in this build.
5
+
6
+ The legacy CLI (``gnr web daemon``) imports ``DaemonService`` here. In the in-process
7
+ build there is no daemon to start/stop, so running that command is an explicit error;
8
+ the import itself resolves so the CLI dispatch table does not break at load time.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ __all__ = ["DaemonService"]
16
+
17
+
18
+ class DaemonService:
19
+ """Stub: there is no register daemon to control in the in-process build."""
20
+
21
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
22
+ self._args = args
23
+
24
+ def run(self) -> None:
25
+ raise RuntimeError(
26
+ "no register daemon in this build: the register is served in-process, "
27
+ "there is nothing to start with `gnr web daemon`"
28
+ )
@@ -0,0 +1,37 @@
1
+ # Copyright 2025 Softwell S.r.l.
2
+ # Licensed under the Apache License, Version 2.0
3
+
4
+ """The ``gnr.web.daemon.siteregister`` face of the in-process register.
5
+
6
+ The legacy imports a handful of names from ``gnr.web.daemon.siteregister``
7
+ (``DEFAULT_PAGE_MAX_AGE``, ``GnrDaemonException``) and its backward-compat shim
8
+ re-exports ``RegisterResolver`` from here too. This module provides exactly those,
9
+ with no daemon server behind them: the register lives inside the SPA application, not
10
+ in a separate ``GnrSiteRegister`` process.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any
16
+
17
+ from .exceptions import GnrDaemonException
18
+
19
+ __all__ = ["DEFAULT_PAGE_MAX_AGE", "GnrDaemonException", "RegisterResolver"]
20
+
21
+ # The page eviction window the legacy cleanup config falls back to (seconds).
22
+ DEFAULT_PAGE_MAX_AGE = 120
23
+
24
+
25
+ class RegisterResolver:
26
+ """Placeholder for the admin-UI lazy resolver (browse users/connections/pages).
27
+
28
+ The legacy re-exports this name; the in-process build does not drive the daemon
29
+ admin browser, so instantiating it is an explicit error rather than a silent
30
+ empty tree. Kept only so the import resolves.
31
+ """
32
+
33
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
34
+ raise NotImplementedError(
35
+ "RegisterResolver (daemon admin browser) is not available in the "
36
+ "in-process siteregister"
37
+ )