ocelescope-backend 0.1.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.
Files changed (66) hide show
  1. ocelescope_backend-0.1.0/PKG-INFO +58 -0
  2. ocelescope_backend-0.1.0/README.md +41 -0
  3. ocelescope_backend-0.1.0/pyproject.toml +31 -0
  4. ocelescope_backend-0.1.0/src/ocelescope_backend/__init__.py +1 -0
  5. ocelescope_backend-0.1.0/src/ocelescope_backend/app/__init__.py +1 -0
  6. ocelescope_backend-0.1.0/src/ocelescope_backend/app/dependencies.py +58 -0
  7. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/__init__.py +0 -0
  8. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/config.py +45 -0
  9. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/discovery/__init__.py +15 -0
  10. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/discovery/bootstrap.py +12 -0
  11. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/discovery/registry.py +137 -0
  12. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/docs.py +58 -0
  13. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/exceptions.py +42 -0
  14. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/logger.py +32 -0
  15. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/__init__.py +0 -0
  16. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/base.py +33 -0
  17. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/discovery.py +38 -0
  18. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/events.py +13 -0
  19. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/ocel.py +141 -0
  20. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/plugin.py +55 -0
  21. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/relations.py +111 -0
  22. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/resource.py +27 -0
  23. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/model/response.py +20 -0
  24. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/ocel/__init__.py +0 -0
  25. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/ocel/default_ocel.py +180 -0
  26. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/registrar.py +18 -0
  27. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/registry/__init__.py +3 -0
  28. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/registry/extension.py +47 -0
  29. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/registry/plugin.py +70 -0
  30. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/registry/registry_manager.py +188 -0
  31. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/registry/resource.py +89 -0
  32. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/session.py +177 -0
  33. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/tasks/__init__.py +0 -0
  34. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/tasks/base.py +80 -0
  35. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/tasks/discovery_task.py +161 -0
  36. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/tasks/plugin.py +187 -0
  37. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/tasks/system.py +170 -0
  38. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/tasks/system_tasks.py +192 -0
  39. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/__init__.py +0 -0
  40. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/cache.py +188 -0
  41. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/dynamic_import.py +56 -0
  42. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/hashing.py +41 -0
  43. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/misc.py +227 -0
  44. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/pandas.py +480 -0
  45. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/plugin_result.py +54 -0
  46. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/relations.py +119 -0
  47. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/spool_upload.py +38 -0
  48. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/util/types.py +13 -0
  49. ocelescope_backend-0.1.0/src/ocelescope_backend/app/internal/utils.py +82 -0
  50. ocelescope_backend-0.1.0/src/ocelescope_backend/app/middleware.py +29 -0
  51. ocelescope_backend-0.1.0/src/ocelescope_backend/app/modules/__init__.py +3 -0
  52. ocelescope_backend-0.1.0/src/ocelescope_backend/app/modules/base.py +27 -0
  53. ocelescope_backend-0.1.0/src/ocelescope_backend/app/modules/loader.py +59 -0
  54. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/__init__.py +16 -0
  55. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/discovery.py +158 -0
  56. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/ocels.py +533 -0
  57. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/plugins.py +267 -0
  58. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/resources.py +126 -0
  59. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/session.py +73 -0
  60. ocelescope_backend-0.1.0/src/ocelescope_backend/app/routes/tasks.py +48 -0
  61. ocelescope_backend-0.1.0/src/ocelescope_backend/app/sse_manager.py +96 -0
  62. ocelescope_backend-0.1.0/src/ocelescope_backend/cli.py +79 -0
  63. ocelescope_backend-0.1.0/src/ocelescope_backend/factory.py +80 -0
  64. ocelescope_backend-0.1.0/src/ocelescope_backend/main.py +3 -0
  65. ocelescope_backend-0.1.0/src/ocelescope_backend/py.typed +0 -0
  66. ocelescope_backend-0.1.0/src/ocelescope_backend/version.py +1 -0
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.3
2
+ Name: ocelescope-backend
3
+ Version: 0.1.0
4
+ Summary: Ocelescope backend host application
5
+ Requires-Dist: pm4py~=2.7.22
6
+ Requires-Dist: pandas>=2.3.1
7
+ Requires-Dist: numpy>=2.3.2
8
+ Requires-Dist: cachetools>=6.1.0
9
+ Requires-Dist: pydantic>=2.11.7
10
+ Requires-Dist: pydantic-settings>=2.2.1,<3
11
+ Requires-Dist: fastapi[standard]>=0.136.0
12
+ Requires-Dist: uvicorn>=0.49,<0.50
13
+ Requires-Dist: ocelescope[plugin]>=0.1.0,<0.2.0
14
+ Requires-Dist: orjson>=3.11.3
15
+ Requires-Python: >=3.11
16
+ Description-Content-Type: text/markdown
17
+
18
+ # ocelescope-backend
19
+
20
+ The Ocelescope backend host application. It exposes the FastAPI server that
21
+ serves OCELs, resources and modules, and provides the `ocelescope-backend`
22
+ command-line interface.
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install ocelescope-backend
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ Start the development server:
33
+
34
+ ```bash
35
+ ocelescope-backend serve
36
+ ```
37
+
38
+ Other commands (e.g. generating an OpenAPI schema for a module) are available
39
+ via:
40
+
41
+ ```bash
42
+ ocelescope-backend --help
43
+ ```
44
+
45
+ ## Modules
46
+
47
+ The backend discovers modules through the `ocelescope_backend.modules` entry
48
+ point group. Installing a module package (for example
49
+ [`ocelescope-module-ocelot`](../modules/ocelescope-module-ocelot)) makes it
50
+ available to the host automatically.
51
+
52
+ ## About
53
+
54
+ Part of [Ocelescope](https://github.com/promi4s/ocelescope), a framework for
55
+ working with Object-Centric Event Logs developed at the Chair of Process and
56
+ Data Science (PADS), RWTH Aachen University.
57
+
58
+ 📖 Documentation: <https://www.ocelescope.org>
@@ -0,0 +1,41 @@
1
+ # ocelescope-backend
2
+
3
+ The Ocelescope backend host application. It exposes the FastAPI server that
4
+ serves OCELs, resources and modules, and provides the `ocelescope-backend`
5
+ command-line interface.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install ocelescope-backend
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ Start the development server:
16
+
17
+ ```bash
18
+ ocelescope-backend serve
19
+ ```
20
+
21
+ Other commands (e.g. generating an OpenAPI schema for a module) are available
22
+ via:
23
+
24
+ ```bash
25
+ ocelescope-backend --help
26
+ ```
27
+
28
+ ## Modules
29
+
30
+ The backend discovers modules through the `ocelescope_backend.modules` entry
31
+ point group. Installing a module package (for example
32
+ [`ocelescope-module-ocelot`](../modules/ocelescope-module-ocelot)) makes it
33
+ available to the host automatically.
34
+
35
+ ## About
36
+
37
+ Part of [Ocelescope](https://github.com/promi4s/ocelescope), a framework for
38
+ working with Object-Centric Event Logs developed at the Chair of Process and
39
+ Data Science (PADS), RWTH Aachen University.
40
+
41
+ 📖 Documentation: <https://www.ocelescope.org>
@@ -0,0 +1,31 @@
1
+ [project]
2
+ name = "ocelescope-backend"
3
+ version = "0.1.0"
4
+ description = "Ocelescope backend host application"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "pm4py~=2.7.22",
9
+ "pandas>=2.3.1",
10
+ "numpy>=2.3.2",
11
+ "cachetools>=6.1.0",
12
+ "pydantic>=2.11.7",
13
+ "pydantic-settings>=2.2.1,<3",
14
+ "fastapi[standard]>=0.136.0",
15
+ "uvicorn>=0.49,<0.50",
16
+ "ocelescope[plugin]>=0.1.0,<0.2.0",
17
+ "orjson>=3.11.3",
18
+ ]
19
+
20
+ [project.scripts]
21
+ ocelescope-backend = "ocelescope_backend.cli:app"
22
+
23
+ [build-system]
24
+ requires = ["uv_build>=0.11.20,<0.12.0"]
25
+ build-backend = "uv_build"
26
+
27
+ [tool.uv]
28
+ package = true
29
+
30
+ [tool.uv.sources]
31
+ ocelescope = { workspace = true }
@@ -0,0 +1 @@
1
+ __all__: list[str] = []
@@ -0,0 +1 @@
1
+ __all__: list[str] = []
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Annotated, Literal
4
+
5
+ from fastapi import Depends, HTTPException, Request
6
+
7
+ from ocelescope import OCEL
8
+ from ocelescope_backend.app.internal.exceptions import NotFound
9
+ from ocelescope_backend.app.internal.session import Session
10
+ from ocelescope_backend.app.internal.tasks.plugin import PluginTask
11
+
12
+
13
+ def get_session(request: Request) -> Session:
14
+ session = getattr(request.state, "session", None)
15
+ if not session:
16
+ raise HTTPException(status_code=500, detail="Session middleware not set")
17
+ return session
18
+
19
+
20
+ ApiSession = Annotated[Session, Depends(get_session)]
21
+
22
+
23
+ def get_ocel(
24
+ session: ApiSession,
25
+ ocel_id: str | None = None,
26
+ ocel_version: Literal["original", "filtered"] | None = "filtered",
27
+ ):
28
+ # Used so the generated react queries don't required them so they can be injected from the session storage
29
+ if not ocel_id:
30
+ raise HTTPException(status_code=500, detail="Ocel id is required")
31
+ try:
32
+ return session.get_ocel(
33
+ ocel_id, use_original=False if ocel_version != "original" else True
34
+ )
35
+ except NotFound:
36
+ raise HTTPException(status_code=404, detail="OCEL not found")
37
+
38
+
39
+ ApiOcel = Annotated[OCEL, Depends(get_ocel)]
40
+
41
+
42
+ def get_plugin_task(
43
+ session: ApiSession, plugin_id: str, method_name: str, task_id: str
44
+ ) -> PluginTask:
45
+ plugin_task = session.get_task(task_id)
46
+
47
+ if (
48
+ plugin_task is None
49
+ or not isinstance(plugin_task, PluginTask)
50
+ or plugin_task.plugin_id != plugin_id
51
+ or plugin_task.method_name != method_name
52
+ ):
53
+ raise NotFound("Task could not be found")
54
+
55
+ return plugin_task
56
+
57
+
58
+ ApiPluginTask = Annotated[PluginTask, Depends(get_plugin_task)]
@@ -0,0 +1,45 @@
1
+ from typing import Literal
2
+
3
+ from pydantic import DirectoryPath, Field
4
+ from pydantic_settings import BaseSettings
5
+
6
+ """
7
+ This file contains a Config class defining all environment parameters, including types, default values and descriptions.
8
+ .env.example (and the structure of .env) should be generated using the `export_settings_as_dotenv` util function.
9
+ """
10
+
11
+
12
+ class OceanConfig(BaseSettings):
13
+ FRONTEND_URL: str = Field(
14
+ default="http://frontend:3000",
15
+ description="The frontend URL, relevant for CORS settings",
16
+ )
17
+
18
+ SESSION_ID_HEADER: str = Field(
19
+ default="ocelescope-session-id",
20
+ description="The HTTP header name containing the session ID.",
21
+ )
22
+
23
+ EXPOSE_ERROR_DETAILS: bool = Field(
24
+ default=False,
25
+ description="When set to True, passes details of internal errors via the API. Always set to False in production environment.",
26
+ )
27
+
28
+ DATA_DIR: DirectoryPath | None = Field(
29
+ default=None,
30
+ description="Path to the data directory, relative to `main.py`",
31
+ )
32
+ PLUGIN_DIR: DirectoryPath | None = Field(
33
+ default=None,
34
+ description="Path to the directory, where plugins are stored",
35
+ )
36
+
37
+ MODE: Literal["production", "development"] | None = Field(
38
+ default="development", description="The mode in which the backend is running"
39
+ )
40
+
41
+ class Config:
42
+ env_file = ".env"
43
+
44
+
45
+ config = OceanConfig() # type: ignore
@@ -0,0 +1,15 @@
1
+ from ocelescope_backend.app.internal.discovery.registry import (
2
+ DiscoveryMethodGroup,
3
+ DiscoveryMethodInfo,
4
+ DiscoveryRegistry,
5
+ discovery_registry,
6
+ register_discovery_methods_from_module,
7
+ )
8
+
9
+ __all__ = [
10
+ "DiscoveryMethodGroup",
11
+ "DiscoveryMethodInfo",
12
+ "DiscoveryRegistry",
13
+ "discovery_registry",
14
+ "register_discovery_methods_from_module",
15
+ ]
@@ -0,0 +1,12 @@
1
+ """Register discovery methods shipped with the ocelescope SDK."""
2
+
3
+ from ocelescope.discovery import algorithms
4
+ from ocelescope_backend.app.internal.discovery.registry import (
5
+ register_discovery_methods_from_module,
6
+ )
7
+
8
+
9
+ def register_builtin_discovery_methods() -> None:
10
+ # The SDK's `algorithms` package re-exports each built-in @discovery_method
11
+ # function; scanning the package module is enough to pick them all up.
12
+ register_discovery_methods_from_module(algorithms)
@@ -0,0 +1,137 @@
1
+ import inspect
2
+ from dataclasses import dataclass
3
+ from types import ModuleType
4
+ from typing import Any, Callable, get_type_hints
5
+ from uuid import uuid4
6
+
7
+ from ocelescope.discovery import DiscoveryMethodMeta
8
+ from ocelescope.ocel import OCEL
9
+ from ocelescope.resource import Resource
10
+ from pydantic import BaseModel, ConfigDict, create_model
11
+
12
+
13
+ def _snake_to_camel(value: str) -> str:
14
+ first, *rest = value.split("_")
15
+ return first + "".join(part.capitalize() for part in rest)
16
+
17
+
18
+ def _build_parameter_model(func: Callable[..., Resource]) -> type[BaseModel]:
19
+ """Build a pydantic model that describes the function's non-OCEL parameters."""
20
+ sig = inspect.signature(func)
21
+ hints = get_type_hints(func, include_extras=True)
22
+
23
+ fields: dict[str, tuple[Any, Any]] = {}
24
+ for name, param in sig.parameters.items():
25
+ if name in ("ocel", "self"):
26
+ continue
27
+ annotation = hints.get(name, param.annotation)
28
+ default = param.default if param.default is not inspect.Parameter.empty else ...
29
+ fields[name] = (annotation, default)
30
+
31
+ config = ConfigDict(
32
+ alias_generator=_snake_to_camel,
33
+ populate_by_name=True,
34
+ arbitrary_types_allowed=True,
35
+ )
36
+
37
+ model_name = f"{func.__name__.title().replace('_', '')}Parameters" # ty: ignore[unresolved-attribute]
38
+ return create_model(model_name, __config__=config, **fields) # type: ignore[call-overload] # ty: ignore[no-matching-overload]
39
+
40
+
41
+ @dataclass
42
+ class DiscoveryMethodInfo:
43
+ method_id: str
44
+ name: str
45
+ description: str | None
46
+ func: Callable[..., Resource]
47
+ parameter_type: type[BaseModel]
48
+ resource_type: type[Resource]
49
+
50
+ def parameters_schema(self) -> dict[str, Any]:
51
+ return self.parameter_type.model_json_schema(by_alias=True)
52
+
53
+ def parse_parameters(self, data: dict[str, Any]) -> BaseModel:
54
+ return self.parameter_type.model_validate(data)
55
+
56
+ def dump_parameters(self, params: BaseModel) -> dict[str, Any]:
57
+ return params.model_dump(by_alias=False)
58
+
59
+ def run(self, *, ocel: OCEL, parameters: BaseModel) -> Resource:
60
+ return self.func(ocel=ocel, **parameters.model_dump(by_alias=False))
61
+
62
+
63
+ class DiscoveryMethodGroup:
64
+ def __init__(
65
+ self,
66
+ name: str,
67
+ variants: list[DiscoveryMethodInfo],
68
+ ) -> None:
69
+ self.name = name
70
+ self.variants = variants
71
+
72
+
73
+ class DiscoveryRegistry:
74
+ def __init__(self) -> None:
75
+ self._methods: dict[str, DiscoveryMethodInfo] = {}
76
+
77
+ def register(self, meta: DiscoveryMethodMeta) -> DiscoveryMethodInfo:
78
+ parameter_type = _build_parameter_model(meta.func)
79
+
80
+ for existing in self._methods.values():
81
+ if (
82
+ existing.name == meta.name
83
+ and existing.resource_type is meta.resource_type
84
+ ):
85
+ raise TypeError(
86
+ f"Discovery method '{meta.name}' is already registered for resource "
87
+ f"type '{meta.resource_type.get_type()}'."
88
+ )
89
+
90
+ info = DiscoveryMethodInfo(
91
+ method_id=str(uuid4()),
92
+ name=meta.name,
93
+ description=meta.description,
94
+ func=meta.func,
95
+ parameter_type=parameter_type,
96
+ resource_type=meta.resource_type,
97
+ )
98
+ self._methods[info.method_id] = info
99
+ return info
100
+
101
+ def unregister_by_func(self, func: Callable[..., Resource]) -> None:
102
+ for method_id, info in list(self._methods.items()):
103
+ if info.func is func:
104
+ del self._methods[method_id]
105
+
106
+ def get(self, method_id: str) -> DiscoveryMethodInfo:
107
+ info = self._methods.get(method_id)
108
+ if info is None:
109
+ raise KeyError(f"Discovery method '{method_id}' is not registered")
110
+ return info
111
+
112
+ def list_groups(self) -> list[DiscoveryMethodGroup]:
113
+ groups: dict[str, DiscoveryMethodGroup] = {}
114
+ for info in self._methods.values():
115
+ if info.name not in groups:
116
+ groups[info.name] = DiscoveryMethodGroup(
117
+ name=info.name,
118
+ variants=[info],
119
+ )
120
+ else:
121
+ groups[info.name].variants.append(info)
122
+ return list(groups.values())
123
+
124
+
125
+ discovery_registry = DiscoveryRegistry()
126
+
127
+
128
+ def register_discovery_methods_from_module(
129
+ module: ModuleType,
130
+ ) -> list[DiscoveryMethodInfo]:
131
+ """Scan a module for functions tagged with `__discovery_meta__` and register them."""
132
+ registered: list[DiscoveryMethodInfo] = []
133
+ for value in vars(module).values():
134
+ meta = getattr(value, "__discovery_meta__", None)
135
+ if isinstance(meta, DiscoveryMethodMeta):
136
+ registered.append(discovery_registry.register(meta))
137
+ return registered
@@ -0,0 +1,58 @@
1
+ # Modifications to the SwaggerUI interface generated by FastAPI
2
+
3
+ # Hide curl commands
4
+ # source: https://github.com/tiangolo/fastapi/discussions/3853#discussioncomment-1388209
5
+
6
+ from typing import Literal
7
+
8
+ from fastapi import FastAPI
9
+ from starlette.responses import HTMLResponse
10
+
11
+ from ocelescope_backend.app.internal.config import config
12
+
13
+
14
+ def get_rapidoc_html(
15
+ *,
16
+ openapi_url: str,
17
+ title: str,
18
+ rapidoc_js_url: str = "https://unpkg.com/rapidoc/dist/rapidoc-min.js",
19
+ rapidoc_favicon_url: str = "https://rapidocweb.com/images/logo.png",
20
+ ):
21
+ return HTMLResponse(
22
+ f"""
23
+ <!doctype html> <!-- Important: must specify -->
24
+ <html>
25
+ <head>
26
+ <meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 characters -->
27
+ <script type="module" src="{rapidoc_js_url}"></script>
28
+ <title>{title}</title>
29
+ <link rel="shortcut icon" href="{rapidoc_favicon_url}">
30
+ </head>
31
+ <body>
32
+ <rapi-doc
33
+ spec-url="{openapi_url}"
34
+ layout="row"
35
+ render-style="view"
36
+ allow-server-selection="false"
37
+ show-header="false"
38
+ show-components="true"
39
+ api-key-name="{config.SESSION_ID_HEADER}"
40
+ api-key-location="header"
41
+ api-key-value="-"
42
+ ></rapi-doc>
43
+ </body>
44
+ </html>
45
+ """
46
+ )
47
+
48
+
49
+ def init_custom_docs(app: FastAPI):
50
+ assert app.openapi_url is not None
51
+
52
+ @app.get("/docs", include_in_schema=False)
53
+ async def docs(ui: Literal["swaggerui", "rapidoc"] = "rapidoc", curl: bool = True):
54
+ if ui == "rapidoc":
55
+ return get_rapidoc_html(
56
+ openapi_url=app.openapi_url or "/",
57
+ title=app.title + " - rapidoc",
58
+ )
@@ -0,0 +1,42 @@
1
+ from fastapi import HTTPException
2
+ from starlette.status import (
3
+ HTTP_400_BAD_REQUEST,
4
+ HTTP_401_UNAUTHORIZED,
5
+ HTTP_402_PAYMENT_REQUIRED,
6
+ HTTP_403_FORBIDDEN,
7
+ HTTP_404_NOT_FOUND,
8
+ HTTP_405_METHOD_NOT_ALLOWED,
9
+ )
10
+
11
+
12
+ class BadRequest(HTTPException):
13
+ def __init__(self, detail: str | None = None):
14
+ super().__init__(status_code=HTTP_400_BAD_REQUEST, detail=detail)
15
+
16
+
17
+ class Unauthorized(HTTPException):
18
+ def __init__(self, detail: str | None = None):
19
+ super().__init__(status_code=HTTP_401_UNAUTHORIZED, detail=detail)
20
+
21
+
22
+ class PaymentRequired(HTTPException):
23
+ def __init__(self, detail: str | None = None):
24
+ super().__init__(status_code=HTTP_402_PAYMENT_REQUIRED, detail=detail)
25
+
26
+
27
+ class Forbidden(HTTPException):
28
+ def __init__(self, detail: str | None = None):
29
+ super().__init__(status_code=HTTP_403_FORBIDDEN, detail=detail)
30
+
31
+
32
+ class NotFound(HTTPException):
33
+ def __init__(self, detail: str | None = None):
34
+ super().__init__(status_code=HTTP_404_NOT_FOUND, detail=detail)
35
+
36
+
37
+ class MethodNotAllowed(HTTPException):
38
+ def __init__(self, detail: str | None = None):
39
+ super().__init__(status_code=HTTP_405_METHOD_NOT_ALLOWED, detail=detail)
40
+
41
+
42
+ # ...
@@ -0,0 +1,32 @@
1
+ import logging
2
+
3
+ import uvicorn.config
4
+
5
+
6
+ class IgnoreOptionsRequestsFilter(logging.Filter):
7
+ def filter(self, record):
8
+ if record.args is None:
9
+ return True
10
+ ip, method, route, _, code = record.args
11
+ return method != "OPTIONS"
12
+
13
+
14
+ ignore_options_request_filter = IgnoreOptionsRequestsFilter("ignore_options_requests")
15
+
16
+
17
+ LOGGER_CONFIG = uvicorn.config.LOGGING_CONFIG
18
+ LOGGER_CONFIG["disable_existing_loggers"] = False
19
+
20
+ # logger = logging.getLogger("ocean")
21
+
22
+ # # Redirect own logger to uvicorn
23
+ # uvicorn_logger = logging.getLogger("uvicorn")
24
+ # for handler in uvicorn_logger.handlers:
25
+ # logger.addHandler(handler)
26
+
27
+ access_logger = logging.getLogger("uvicorn.access")
28
+ access_logger.addFilter(ignore_options_request_filter)
29
+
30
+ logger = logging.getLogger("uvicorn.error")
31
+ # logger.setLevel(logging.INFO)
32
+ logger.setLevel(logging.DEBUG)
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Generic, TypeVar
4
+
5
+ from pydantic import BaseModel, computed_field
6
+
7
+ from ..utils import custom_snake2camel
8
+
9
+
10
+ class ApiBaseModel(BaseModel):
11
+ class Config:
12
+ alias_generator = custom_snake2camel
13
+ populate_by_name = True
14
+ arbitrary_types_allowed = True
15
+
16
+
17
+ class RequestBody(ApiBaseModel):
18
+ pass
19
+
20
+
21
+ T = TypeVar("T")
22
+
23
+
24
+ class PaginatedResponse(BaseModel, Generic[T]):
25
+ response: T
26
+ page: int
27
+ page_size: int
28
+ total_items: int
29
+
30
+ @computed_field
31
+ @property
32
+ def total_pages(self) -> int:
33
+ return self.total_items // self.page_size
@@ -0,0 +1,38 @@
1
+ from typing import Any
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+ from ocelescope import BaseFilter
6
+ from ocelescope_backend.app.internal.model.base import ApiBaseModel, RequestBody
7
+
8
+
9
+ class FilterEnvelope(ApiBaseModel):
10
+ name: str
11
+ payload: dict[str, Any] = Field(default_factory=dict)
12
+
13
+
14
+ class DiscoveryRequest(BaseModel):
15
+ ocel_id: str
16
+ method_id: str
17
+ name: str
18
+ resource_type: str
19
+ parameters: dict[str, Any] = Field(default_factory=dict)
20
+ filters: list[BaseFilter] = Field(default_factory=list)
21
+
22
+
23
+ class CreateDiscoveryTaskBody(RequestBody):
24
+ method_id: str
25
+ parameters: dict[str, Any] = Field(default_factory=dict)
26
+ filters: list[FilterEnvelope] = Field(default_factory=list)
27
+
28
+
29
+ class DiscoveryVariant(ApiBaseModel):
30
+ method_id: str
31
+ resource_type: str
32
+ input_schema: dict[str, Any]
33
+ description: str | None = None
34
+
35
+
36
+ class DiscoveryMethodMeta(ApiBaseModel):
37
+ name: str
38
+ variants: list[DiscoveryVariant]
@@ -0,0 +1,13 @@
1
+ from pydantic import BaseModel
2
+
3
+
4
+ class Date_Distribution_Item(BaseModel):
5
+ start_timestamp: str
6
+ end_timestamp: str
7
+ entity_count: dict[str, int]
8
+
9
+
10
+ class Entity_Time_Info(BaseModel):
11
+ start_time: str
12
+ end_time: str
13
+ date_distribution: list[Date_Distribution_Item]