Skip to content

Functions api ✨ 🗃️ #7539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 72 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
1b1e8fe
Add functions api. New commit to clean up db migration
wvangeit Apr 16, 2025
080e4fb
Add db migration script for functions api
wvangeit Apr 16, 2025
db68658
Add functions api. New commit to clean up db migration
wvangeit May 6, 2025
0170982
Add db migration script for functions api
wvangeit Apr 16, 2025
acdcfd4
Add solver functions
wvangeit Apr 16, 2025
8fbd814
Add default inputs to functions
wvangeit Apr 16, 2025
b5cf30a
Add default inputs to functions
wvangeit Apr 16, 2025
4e1ac9a
Add function collections
wvangeit Apr 29, 2025
9fabebf
Working project function job collection
wvangeit Apr 29, 2025
3c9f819
Add db migration for job collections
wvangeit Apr 29, 2025
77e0a00
Adapt for changes in solver job api
wvangeit Apr 29, 2025
b8722cc
Db merge heads functions draft and master rebase
wvangeit Apr 29, 2025
efdd470
Add tests for functions api server
wvangeit Apr 30, 2025
be9ac7c
Add function rpc tests
wvangeit Apr 30, 2025
6a7ba23
Move function rpc test dir
wvangeit Apr 30, 2025
23b36e9
Remove Nullable fields
wvangeit May 7, 2025
f956360
Merge alembic heads after rebase
wvangeit May 7, 2025
94c44c8
Fix tests after rebase
wvangeit May 7, 2025
61c3bbf
Add pagenation to function listing
wvangeit May 7, 2025
9f3d968
Fix function routes
wvangeit May 7, 2025
8619a2c
Fix function pagination api tests
wvangeit May 7, 2025
8042578
Fix pagination of functions again
wvangeit May 7, 2025
dc39523
Restore some files from master
wvangeit May 8, 2025
4022abb
Merge branch 'functions_draft_tests' into functions_draft
wvangeit May 8, 2025
d246bb4
Fix pylint
wvangeit May 8, 2025
616bf69
Changes based on Mads comments wrt functions api
wvangeit May 8, 2025
3da5899
Mention explicit exceptions in functions rpc
wvangeit May 8, 2025
ced82aa
Fix linting
wvangeit May 8, 2025
7e74a60
Add assert checks in functions rpc interface
wvangeit May 8, 2025
c1660c6
Fix gh action tests
wvangeit May 8, 2025
d4ef130
Add types jsonschema to api-server test requirements
wvangeit May 8, 2025
4e10cc7
Fix functions rpc assert and delete functions.py old schema
wvangeit May 8, 2025
188b0da
Add functions api. New commit to clean up db migration
wvangeit May 8, 2025
2feedee
Add db migration script for functions api
wvangeit Apr 16, 2025
f9f4a67
Add solver functions
wvangeit Apr 16, 2025
a5b5a76
Add default inputs to functions
wvangeit Apr 16, 2025
1f24485
Add default inputs to functions
wvangeit Apr 16, 2025
b38daa2
Add function collections
wvangeit Apr 29, 2025
ef3ce7b
Working project function job collection
wvangeit Apr 29, 2025
3f5ea5c
Add db migration for job collections
wvangeit Apr 29, 2025
5031f89
Adapt for changes in solver job api
wvangeit Apr 29, 2025
6957801
Db merge heads functions draft and master rebase
wvangeit Apr 29, 2025
baa5bfb
Add tests for functions api server
wvangeit Apr 30, 2025
c4aad00
Add function rpc tests
wvangeit Apr 30, 2025
b5421f2
Move function rpc test dir
wvangeit Apr 30, 2025
b061250
Remove Nullable fields
wvangeit May 7, 2025
e2d1da0
Merge alembic heads after rebase
wvangeit May 7, 2025
df546b3
Fix tests after rebase
wvangeit May 7, 2025
90dc711
Add pagenation to function listing
wvangeit May 7, 2025
d8393d4
Fix function routes
wvangeit May 7, 2025
e71932a
Fix function pagination api tests
wvangeit May 7, 2025
af697f2
Fix pagination of functions again
wvangeit May 7, 2025
bede7bd
Restore some files from master
wvangeit May 8, 2025
f772f04
Fix pylint
wvangeit May 8, 2025
fae8553
Changes based on Mads comments wrt functions api
wvangeit May 8, 2025
a8fbdf1
Mention explicit exceptions in functions rpc
wvangeit May 8, 2025
b683bc5
Fix linting
wvangeit May 8, 2025
e0d186b
Add assert checks in functions rpc interface
wvangeit May 8, 2025
9b2ff02
Fix gh action tests
wvangeit May 8, 2025
61dcec3
Add types jsonschema to api-server test requirements
wvangeit May 8, 2025
9b1e0a7
Fix functions rpc assert and delete functions.py old schema
wvangeit May 8, 2025
53f7029
Merge branch 'functions_draft' of github.com:wvangeit/osparc-simcore …
wvangeit May 8, 2025
f1041e1
Changes suggested by Sylvain wrt to functions api+new function schema
wvangeit May 9, 2025
1c22143
Refactor function db files based on SA's suggestion
wvangeit May 9, 2025
c548293
Fix db names in function repo
wvangeit May 9, 2025
489dd92
Rename function tables primary keys
wvangeit May 9, 2025
f1e9a63
Remove pk constraints from a functions table
wvangeit May 9, 2025
df84db9
Add migration script for renaming funcapi tables
wvangeit May 9, 2025
0c4980a
Merge branch 'master' into functions_draft
wvangeit May 9, 2025
810a1fa
Delete db migrates for funcapi to cleanup
wvangeit May 9, 2025
8bfaa2a
Add db migration script for functions_api
wvangeit May 9, 2025
98fa066
Run isort on function files
wvangeit May 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
from collections.abc import Mapping
from enum import Enum
from typing import Annotated, Any, Literal, TypeAlias
from uuid import UUID

from models_library import projects
from models_library.services_types import ServiceKey, ServiceVersion
from pydantic import BaseModel, Field

from ..projects import ProjectID

FunctionID: TypeAlias = UUID
FunctionJobID: TypeAlias = UUID
FileID: TypeAlias = UUID

InputTypes: TypeAlias = FileID | float | int | bool | str | list


class FunctionSchemaClass(str, Enum):
json_schema = "application/schema+json"


class FunctionSchemaBase(BaseModel):
schema_content: Any = Field(default=None)
schema_class: FunctionSchemaClass


class JSONFunctionSchema(FunctionSchemaBase):
schema_content: Mapping[str, Any] = Field(
default={}, description="JSON Schema", title="JSON Schema"
) # json-schema library defines a schema as Mapping[str, Any]
schema_class: FunctionSchemaClass = FunctionSchemaClass.json_schema


class JSONFunctionInputSchema(JSONFunctionSchema):
schema_class: Literal[FunctionSchemaClass.json_schema] = (
FunctionSchemaClass.json_schema
)


class JSONFunctionOutputSchema(JSONFunctionSchema):
schema_class: Literal[FunctionSchemaClass.json_schema] = (
FunctionSchemaClass.json_schema
)


FunctionInputSchema: TypeAlias = Annotated[
JSONFunctionInputSchema,
Field(discriminator="schema_class"),
]

FunctionOutputSchema: TypeAlias = Annotated[
JSONFunctionOutputSchema,
Field(discriminator="schema_class"),
]


class FunctionClass(str, Enum):
project = "project"
solver = "solver"
python_code = "python_code"


FunctionClassSpecificData: TypeAlias = dict[str, Any]
FunctionJobClassSpecificData: TypeAlias = FunctionClassSpecificData


# TODO, use InputTypes here, but api is throwing weird errors and asking for dict for elements # noqa: FIX002
FunctionInputs: TypeAlias = dict[str, Any] | None

FunctionInputsList: TypeAlias = list[FunctionInputs]

FunctionOutputs: TypeAlias = dict[str, Any] | None

FunctionOutputsLogfile: TypeAlias = Any


class FunctionBase(BaseModel):
function_class: FunctionClass
uid: FunctionID | None
title: str = ""
description: str = ""
input_schema: FunctionInputSchema
output_schema: FunctionOutputSchema
default_inputs: FunctionInputs


class FunctionDB(BaseModel):
function_class: FunctionClass
uuid: FunctionJobID | None
title: str = ""
description: str = ""
input_schema: FunctionInputSchema
output_schema: FunctionOutputSchema
default_inputs: FunctionInputs
class_specific_data: FunctionClassSpecificData


class FunctionJobDB(BaseModel):
uuid: FunctionJobID | None
function_uuid: FunctionID
title: str = ""
inputs: FunctionInputs
outputs: FunctionOutputs
class_specific_data: FunctionJobClassSpecificData
function_class: FunctionClass


class ProjectFunction(FunctionBase):
function_class: Literal[FunctionClass.project] = FunctionClass.project
project_id: ProjectID


SolverJobID: TypeAlias = UUID


class SolverFunction(FunctionBase):
function_class: Literal[FunctionClass.solver] = FunctionClass.solver
solver_key: ServiceKey
solver_version: ServiceVersion


class PythonCodeFunction(FunctionBase):
function_class: Literal[FunctionClass.python_code] = FunctionClass.python_code
code_url: str


Function: TypeAlias = Annotated[
ProjectFunction | PythonCodeFunction | SolverFunction,
Field(discriminator="function_class"),
]

FunctionJobCollectionID: TypeAlias = projects.ProjectID


class FunctionJobBase(BaseModel):
uid: FunctionJobID | None
title: str = ""
description: str = ""
function_uid: FunctionID
inputs: FunctionInputs
outputs: FunctionOutputs
function_class: FunctionClass


class ProjectFunctionJob(FunctionJobBase):
function_class: Literal[FunctionClass.project] = FunctionClass.project
project_job_id: ProjectID


class SolverFunctionJob(FunctionJobBase):
function_class: Literal[FunctionClass.solver] = FunctionClass.solver
solver_job_id: ProjectID


class PythonCodeFunctionJob(FunctionJobBase):
function_class: Literal[FunctionClass.python_code] = FunctionClass.python_code


FunctionJob: TypeAlias = Annotated[
ProjectFunctionJob | PythonCodeFunctionJob | SolverFunctionJob,
Field(discriminator="function_class"),
]


class FunctionJobStatus(BaseModel):
status: str


class FunctionJobCollection(BaseModel):
"""Model for a collection of function jobs"""

uid: FunctionJobCollectionID | None
title: str = ""
description: str = ""
job_ids: list[FunctionJobID]


class FunctionJobCollectionDB(BaseModel):
"""Model for a collection of function jobs"""

uuid: FunctionJobCollectionID
title: str = ""
description: str = ""


class FunctionJobCollectionStatus(BaseModel):
status: list[str]


class FunctionIDNotFoundError(Exception):
"""Exception raised when a function is not found"""

def __init__(self, function_id: FunctionID):
self.function_id = function_id
super().__init__(f"Function {function_id} not found")


class FunctionJobIDNotFoundError(Exception):
"""Exception raised when a function job is not found"""

def __init__(self, function_job_id: FunctionJobID):
self.function_job_id = function_job_id
super().__init__(f"Function job {function_job_id} not found")


class FunctionJobCollectionIDNotFoundError(Exception):
"""Exception raised when a function job collection is not found"""

def __init__(self, function_job_collection_id: FunctionJobCollectionID):
self.function_job_collection_id = function_job_collection_id
super().__init__(
f"Function job collection {function_job_collection_id} not found"
)


class RegisterFunctionWithIDError(Exception):
"""Exception raised when registering a function with a UID"""

def __init__(self):
super().__init__("Cannot register Function with a UID")


class RegisterFunctionJobWithIDError(Exception):
"""Exception raised when registering a function job with a UID"""

def __init__(self):
super().__init__("Cannot register FunctionJob with a UID")


class RegisterFunctionJobCollectionWithIDError(Exception):
"""Exception raised when registering a function job collection with a UID"""

def __init__(self):
super().__init__("Cannot register FunctionJobCollection with a UID")


class UnsupportedFunctionClassError(Exception):
"""Exception raised when a function class is not supported"""

def __init__(self, function_class: str):
self.function_class = function_class
super().__init__(f"Function class {function_class} is not supported")


class UnsupportedFunctionJobClassError(Exception):
"""Exception raised when a function job class is not supported"""

def __init__(self, function_job_class: str):
self.function_job_class = function_job_class
super().__init__(f"Function job class {function_job_class} is not supported")


class UnsupportedFunctionFunctionJobClassCombinationError(Exception):
"""Exception raised when a function / function job class combination is not supported"""

def __init__(self, function_class: str, function_job_class: str):
self.function_class = function_class
self.function_job_class = function_job_class
super().__init__(
f"Function class {function_class} and function job class {function_job_class} combination is not supported"
)


class FunctionInputsValidationError(Exception):
"""Exception raised when validating function inputs"""

def __init__(self, error: str):
self.errors = error
super().__init__(f"Function inputs validation failed: {error}")
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
"""Add function api tables

Revision ID: 44f40f1069aa
Revises: 0d52976dc616
Create Date: 2025-05-09 13:12:31.423832+00:00

"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "44f40f1069aa"
down_revision = "0d52976dc616"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"funcapi_function_job_collections",
sa.Column("uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("title", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.PrimaryKeyConstraint("uuid", name="funcapi_function_job_collections_pk"),
)
op.create_index(
op.f("ix_funcapi_function_job_collections_uuid"),
"funcapi_function_job_collections",
["uuid"],
unique=False,
)
op.create_table(
"funcapi_functions",
sa.Column("uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("title", sa.String(), nullable=True),
sa.Column("function_class", sa.String(), nullable=True),
sa.Column("description", sa.String(), nullable=True),
sa.Column("input_schema", sa.JSON(), nullable=True),
sa.Column("output_schema", sa.JSON(), nullable=True),
sa.Column("system_tags", sa.JSON(), nullable=True),
sa.Column("user_tags", sa.JSON(), nullable=True),
sa.Column("class_specific_data", sa.JSON(), nullable=True),
sa.Column("default_inputs", sa.JSON(), nullable=True),
sa.PrimaryKeyConstraint("uuid", name="funcapi_functions_pk"),
)
op.create_index(
op.f("ix_funcapi_functions_uuid"), "funcapi_functions", ["uuid"], unique=False
)
op.create_table(
"funcapi_function_jobs",
sa.Column("uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("title", sa.String(), nullable=True),
sa.Column("function_uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column("function_class", sa.String(), nullable=True),
sa.Column("status", sa.String(), nullable=True),
sa.Column("inputs", sa.JSON(), nullable=True),
sa.Column("outputs", sa.JSON(), nullable=True),
sa.Column("class_specific_data", sa.JSON(), nullable=True),
sa.ForeignKeyConstraint(
["function_uuid"],
["funcapi_functions.uuid"],
name="fk_function_jobs_to_function_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("uuid", name="funcapi_function_jobs_pk"),
)
op.create_index(
op.f("ix_funcapi_function_jobs_function_uuid"),
"funcapi_function_jobs",
["function_uuid"],
unique=False,
)
op.create_index(
op.f("ix_funcapi_function_jobs_uuid"),
"funcapi_function_jobs",
["uuid"],
unique=False,
)
op.create_table(
"funcapi_function_job_collections_to_function_jobs",
sa.Column(
"function_job_collection_uuid", postgresql.UUID(as_uuid=True), nullable=True
),
sa.Column("function_job_uuid", postgresql.UUID(as_uuid=True), nullable=True),
sa.ForeignKeyConstraint(
["function_job_collection_uuid"],
["funcapi_function_job_collections.uuid"],
name="fk_func_job_coll_to_func_jobs_to_func_job_coll_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
sa.ForeignKeyConstraint(
["function_job_uuid"],
["funcapi_function_jobs.uuid"],
name="fk_func_job_coll_to_func_jobs_to_func_job_uuid",
onupdate="CASCADE",
ondelete="CASCADE",
),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("funcapi_function_job_collections_to_function_jobs")
op.drop_index(
op.f("ix_funcapi_function_jobs_uuid"), table_name="funcapi_function_jobs"
)
op.drop_index(
op.f("ix_funcapi_function_jobs_function_uuid"),
table_name="funcapi_function_jobs",
)
op.drop_table("funcapi_function_jobs")
op.drop_index(op.f("ix_funcapi_functions_uuid"), table_name="funcapi_functions")
op.drop_table("funcapi_functions")
op.drop_index(
op.f("ix_funcapi_function_job_collections_uuid"),
table_name="funcapi_function_job_collections",
)
op.drop_table("funcapi_function_job_collections")
# ### end Alembic commands ###
Loading
Loading