Skip to content

Commit 618130d

Browse files
authored
Merge pull request #117 from OpenSemanticLab/dev
feat: route Prefect API through MW ApiGateway with httpx transport
2 parents d292d36 + 86cec7f commit 618130d

3 files changed

Lines changed: 341 additions & 31 deletions

File tree

examples/prefect/hello_world.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,44 @@
11
"""Dummy Prefect workflow that prints a message on an OSW page.
22
3-
Usage (PowerShell):
4-
$env:PREFECT_API_URL="https://osw.example.com:4200/api"
5-
$env:PREFECT_PUBLIC_URL="https://osw.example.com/w/rest.php/apigateway/v1/prefect"
6-
$env:OSW_USER="Bot@name"
7-
$env:OSW_SERVER="osw.example.com"
8-
$env:OSW_PASSWORD="bot-password"
9-
python examples/prefect/hello_world.py
10-
11-
Usage (Bash):
12-
export PREFECT_API_URL="https://osw.example.com:4200/api"
13-
export PREFECT_PUBLIC_URL="https://osw.example.com/w/rest.php/apigateway/v1/prefect"
14-
export OSW_USER="Bot@name"
15-
export OSW_SERVER="osw.example.com"
16-
export OSW_PASSWORD="bot-password"
17-
python examples/prefect/hello_world.py
3+
Option A — Direct Prefect access (worker can reach Prefect server directly):
4+
5+
PowerShell:
6+
$env:PREFECT_API_URL="https://osw.example.com:4200/api"
7+
$env:PREFECT_PUBLIC_URL="https://osw.example.com/w/rest.php/apigateway/v1/prefect"
8+
$env:OSW_USER="Bot@name"; $env:OSW_SERVER="osw.example.com"
9+
$env:OSW_PASSWORD="bot-password"
10+
python examples/prefect/hello_world.py
11+
12+
Bash:
13+
export PREFECT_API_URL="https://osw.example.com:4200/api"
14+
export PREFECT_PUBLIC_URL="https://osw.example.com/w/rest.php/apigateway/v1/prefect"
15+
export OSW_USER="Bot@name" OSW_SERVER="osw.example.com"
16+
export OSW_PASSWORD="bot-password"
17+
python examples/prefect/hello_world.py
18+
19+
Option B — ApiGateway only (Prefect server behind firewall, only reachable
20+
through MediaWiki ApiGateway extension):
21+
22+
PowerShell:
23+
$env:PREFECT_API_URL="https://osw.example.com/w/rest.php/apigateway/v1/prefect"
24+
$env:OSW_USER="Bot@name"; $env:OSW_SERVER="osw.example.com"
25+
$env:OSW_PASSWORD="bot-password"
26+
python examples/prefect/hello_world.py
27+
28+
Bash:
29+
export PREFECT_API_URL="https://osw.example.com/w/rest.php/apigateway/v1/prefect"
30+
export OSW_USER="Bot@name" OSW_SERVER="osw.example.com"
31+
export OSW_PASSWORD="bot-password"
32+
python examples/prefect/hello_world.py
1833
1934
Environment variables:
20-
PREFECT_API_URL Prefect server API URL (used by the worker)
35+
PREFECT_API_URL Prefect server API URL (used by the worker).
36+
Can be a direct URL or an ApiGateway URL.
2137
PREFECT_PUBLIC_URL (optional) Public URL stored in PrefectFlow entity,
22-
for use by browser clients (e.g. prefect.js).
38+
for browser clients (e.g. prefect.js).
2339
Falls back to PREFECT_API_URL if not set.
40+
Only needed when PREFECT_API_URL differs from the
41+
public gateway URL (Option A).
2442
OSW_USER OSW bot username
2543
OSW_SERVER OSW instance domain
2644
OSW_PASSWORD OSW bot password. If not set, falls back to a

src/osw/utils/_httpx_gateway.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Auto-patches httpx for ApiGateway routing in Prefect subprocesses.
2+
3+
Activated by osw-httpx-gateway.pth in site-packages. Only patches if
4+
PREFECT_API_URL contains an ApiGateway URL pattern. Login is lazy —
5+
no network calls at import time. All ``osw`` imports are deferred to
6+
first request so the hook works even before editable installs are on
7+
sys.path.
8+
"""
9+
10+
import os
11+
12+
13+
def _install():
14+
api_url = os.environ.get("PREFECT_API_URL", "")
15+
if "/rest.php/apigateway/" not in api_url:
16+
return
17+
18+
if not os.environ.get("OSW_SERVER"):
19+
return
20+
21+
try:
22+
import httpx
23+
except ImportError:
24+
return
25+
26+
class _LazyApiGatewayTransport(httpx.AsyncBaseTransport):
27+
"""Wraps ApiGatewayTransport with lazy MW login on first request."""
28+
29+
def __init__(self, gateway_url):
30+
self._gateway_url = gateway_url
31+
self._inner = None
32+
33+
def _ensure_initialized(self):
34+
if self._inner is not None:
35+
return
36+
# Deferred import — osw may not be on sys.path at .pth time
37+
from osw.utils.workflow import ApiGatewayTransport, connect
38+
39+
osw_instance = connect()
40+
self._inner = ApiGatewayTransport(
41+
gateway_url=self._gateway_url,
42+
mw_site=osw_instance.site.mw_site,
43+
)
44+
45+
async def handle_async_request(self, request):
46+
self._ensure_initialized()
47+
return await self._inner.handle_async_request(request)
48+
49+
_transport = _LazyApiGatewayTransport(api_url)
50+
_original_init = httpx.AsyncClient.__init__
51+
52+
def _patched_init(self, *args, **kwargs):
53+
base = str(kwargs.get("base_url", ""))
54+
if "/rest.php/apigateway/" in base and "transport" not in kwargs:
55+
kwargs["transport"] = _transport
56+
_original_init(self, *args, **kwargs)
57+
58+
httpx.AsyncClient.__init__ = _patched_init
59+
60+
61+
_install()

0 commit comments

Comments
 (0)