Skip to content

Commit 0f7b7fa

Browse files
authored
Merge pull request #2 from masci/load-pipelines-at-startup
Load pipelines at startup
2 parents 4c89fc8 + 351efdc commit 0f7b7fa

File tree

5 files changed

+104
-69
lines changed

5 files changed

+104
-69
lines changed

src/hayhooks/cli/run/__init__.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import click
22
import uvicorn
3+
import os
34

45

56
@click.command()
6-
@click.argument("host", default="localhost")
7-
@click.argument('port', default=1416)
8-
def run(host, port):
7+
@click.option('--host', default="localhost")
8+
@click.option('--port', default=1416)
9+
@click.option('--pipelines-dir', default="pipelines.d")
10+
def run(host, port, pipelines_dir):
11+
os.environ["PIPELINES_DIR"] = pipelines_dir
912
uvicorn.run("hayhooks.server:app", host=host, port=port)

src/hayhooks/server/app.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,30 @@
11
from fastapi import FastAPI
2+
import os
3+
import glob
4+
from pathlib import Path
5+
from hayhooks.server.utils.deploy_utils import deploy_pipeline_def, PipelineDefinition
6+
import logging
27

3-
app = FastAPI()
8+
logger = logging.getLogger("uvicorn.info")
9+
10+
11+
def create_app():
12+
app = FastAPI()
13+
14+
# Deploy all pipelines in the pipelines directory
15+
pipelines_dir = os.environ.get("PIPELINES_DIR")
16+
for pipeline_file_path in glob.glob(f"{pipelines_dir}/*.yml"):
17+
name = Path(pipeline_file_path).stem
18+
with open(pipeline_file_path, "r") as pipeline_file:
19+
source_code = pipeline_file.read()
20+
21+
pipeline_defintion = PipelineDefinition(name=name, source_code=source_code)
22+
deployed_pipeline = deploy_pipeline_def(app, pipeline_defintion)
23+
logger.info(f"Deployed pipeline: {deployed_pipeline['name']}")
24+
return app
25+
26+
27+
app = create_app()
428

529

630
@app.get("/")
Lines changed: 2 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,7 @@
1-
from fastapi import HTTPException
2-
from fastapi.responses import JSONResponse
3-
from pydantic import BaseModel, create_model
4-
51
from hayhooks.server import app
6-
from hayhooks.server.pipelines import registry
7-
8-
9-
class PipelineDefinition(BaseModel):
10-
name: str
11-
source_code: str
2+
from hayhooks.server.utils.deploy_utils import deploy_pipeline_def, PipelineDefinition
123

134

145
@app.post("/deploy")
156
async def deploy(pipeline_def: PipelineDefinition):
16-
try:
17-
pipe = registry.add(pipeline_def.name, pipeline_def.source_code)
18-
except ValueError as e:
19-
raise HTTPException(status_code=409, detail=f"{e}") from e
20-
21-
request_model = {}
22-
for component_name, inputs in pipe.inputs().items():
23-
# Inputs have this form:
24-
# {
25-
# 'first_addition': { <-- Component Name
26-
# 'value': {'type': <class 'int'>, 'is_mandatory': True}, <-- Input
27-
# 'add': {'type': typing.Optional[int], 'is_mandatory': False, 'default_value': None}, <-- Input
28-
# },
29-
# 'second_addition': {'add': {'type': typing.Optional[int], 'is_mandatory': False}},
30-
# }
31-
component_model = {}
32-
for name, typedef in inputs.items():
33-
component_model[name] = (typedef["type"], typedef.get("default_value", ...))
34-
request_model[component_name] = (create_model('ComponentParams', **component_model), ...)
35-
36-
PipelineRunRequest = create_model(f'{pipeline_def.name.capitalize()}RunRequest', **request_model)
37-
38-
response_model = {}
39-
for component_name, outputs in pipe.outputs().items():
40-
# Outputs have this form:
41-
# {
42-
# 'second_addition': { <-- Component Name
43-
# 'result': {'type': "<class 'int'>"} <-- Output
44-
# },
45-
# }
46-
component_model = {}
47-
for name, typedef in outputs.items():
48-
component_model[name] = (typedef["type"], ...)
49-
response_model[component_name] = (create_model('ComponentParams', **component_model), ...)
50-
51-
PipelineRunResponse = create_model(f'{pipeline_def.name.capitalize()}RunResponse', **response_model)
52-
53-
# There's no way in FastAPI to define the type of the request body other than annotating
54-
# the endpoint handler. We have to ignore the type here to make FastAPI happy while
55-
# silencing static type checkers (that would have good reasons to trigger!).
56-
async def pipeline_run(pipeline_run_req: PipelineRunRequest) -> JSONResponse: # type: ignore
57-
output = pipe.run(data=pipeline_run_req.dict())
58-
return JSONResponse(PipelineRunResponse(**output).model_dump(), status_code=200)
59-
60-
app.add_api_route(
61-
path=f"/{pipeline_def.name}",
62-
endpoint=pipeline_run,
63-
methods=["POST"],
64-
name=pipeline_def.name,
65-
response_model=PipelineRunResponse,
66-
)
67-
app.openapi_schema = None
68-
app.setup()
69-
70-
return {"name": pipeline_def.name}
7+
return await deploy_pipeline_def(app, pipeline_def)

src/hayhooks/server/utils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from hayhooks.server.utils import deploy_utils
2+
3+
__all__ = ["deploy_utils"]
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
from fastapi import HTTPException
2+
from fastapi.responses import JSONResponse
3+
from pydantic import BaseModel, create_model
4+
5+
from hayhooks.server.pipelines import registry
6+
7+
8+
class PipelineDefinition(BaseModel):
9+
name: str
10+
source_code: str
11+
12+
13+
def deploy_pipeline_def(app, pipeline_def: PipelineDefinition):
14+
try:
15+
pipe = registry.add(pipeline_def.name, pipeline_def.source_code)
16+
except ValueError as e:
17+
raise HTTPException(status_code=409, detail=f"{e}") from e
18+
19+
request_model = {}
20+
for component_name, inputs in pipe.inputs().items():
21+
# Inputs have this form:
22+
# {
23+
# 'first_addition': { <-- Component Name
24+
# 'value': {'type': <class 'int'>, 'is_mandatory': True}, <-- Input
25+
# 'add': {'type': typing.Optional[int], 'is_mandatory': False, 'default_value': None}, <-- Input
26+
# },
27+
# 'second_addition': {'add': {'type': typing.Optional[int], 'is_mandatory': False}},
28+
# }
29+
component_model = {}
30+
for name, typedef in inputs.items():
31+
component_model[name] = (typedef["type"], typedef.get("default_value", ...))
32+
request_model[component_name] = (create_model('ComponentParams', **component_model), ...)
33+
34+
PipelineRunRequest = create_model(f'{pipeline_def.name.capitalize()}RunRequest', **request_model)
35+
36+
response_model = {}
37+
for component_name, outputs in pipe.outputs().items():
38+
# Outputs have this form:
39+
# {
40+
# 'second_addition': { <-- Component Name
41+
# 'result': {'type': "<class 'int'>"} <-- Output
42+
# },
43+
# }
44+
component_model = {}
45+
for name, typedef in outputs.items():
46+
component_model[name] = (typedef["type"], ...)
47+
response_model[component_name] = (create_model('ComponentParams', **component_model), ...)
48+
49+
PipelineRunResponse = create_model(f'{pipeline_def.name.capitalize()}RunResponse', **response_model)
50+
51+
# There's no way in FastAPI to define the type of the request body other than annotating
52+
# the endpoint handler. We have to ignore the type here to make FastAPI happy while
53+
# silencing static type checkers (that would have good reasons to trigger!).
54+
async def pipeline_run(pipeline_run_req: PipelineRunRequest) -> JSONResponse: # type: ignore
55+
output = pipe.run(data=pipeline_run_req.dict())
56+
return JSONResponse(PipelineRunResponse(**output).model_dump(), status_code=200)
57+
58+
app.add_api_route(
59+
path=f"/{pipeline_def.name}",
60+
endpoint=pipeline_run,
61+
methods=["POST"],
62+
name=pipeline_def.name,
63+
response_model=PipelineRunResponse,
64+
)
65+
app.openapi_schema = None
66+
app.setup()
67+
68+
return {"name": pipeline_def.name}

0 commit comments

Comments
 (0)