interloper-api 0.112.0__tar.gz → 0.113.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 (34) hide show
  1. {interloper_api-0.112.0 → interloper_api-0.113.0}/PKG-INFO +1 -1
  2. {interloper_api-0.112.0 → interloper_api-0.113.0}/pyproject.toml +4 -1
  3. {interloper_api-0.112.0 → interloper_api-0.113.0}/pyproject.toml.orig +4 -1
  4. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/__init__.py +2 -1
  5. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/app.py +4 -4
  6. interloper_api-0.113.0/src/interloper_api/cli.py +62 -0
  7. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/dependencies/rbac.py +1 -2
  8. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/dependencies/state.py +1 -1
  9. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/admin.py +1 -1
  10. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/auth.py +1 -2
  11. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/catalog.py +3 -4
  12. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/components.py +2 -2
  13. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/overview.py +1 -1
  14. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/tokens.py +1 -2
  15. interloper_api-0.113.0/src/interloper_api/server.py +72 -0
  16. {interloper_api-0.112.0 → interloper_api-0.113.0}/README.md +0 -0
  17. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/dependencies/__init__.py +0 -0
  18. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/dependencies/auth.py +0 -0
  19. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/notifications/__init__.py +0 -0
  20. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/notifications/base.py +0 -0
  21. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/notifications/invitations.py +0 -0
  22. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/notifications/super_admins.py +0 -0
  23. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/__init__.py +0 -0
  24. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/agent.py +0 -0
  25. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/backfills.py +0 -0
  26. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/executions.py +0 -0
  27. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/health.py +0 -0
  28. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/oauth.py +0 -0
  29. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/organisations.py +0 -0
  30. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/relations.py +0 -0
  31. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/runs.py +0 -0
  32. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/routes/websocket.py +0 -0
  33. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/utils/__init__.py +0 -0
  34. {interloper_api-0.112.0 → interloper_api-0.113.0}/src/interloper_api/utils/time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: interloper-api
3
- Version: 0.112.0
3
+ Version: 0.113.0
4
4
  Summary: Interloper FastAPI routes
5
5
  Author: Guillaume Onfroy
6
6
  Author-email: Guillaume Onfroy <guillaume@digitlcloud.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "interloper-api"
3
- version = "0.112.0"
3
+ version = "0.113.0"
4
4
  description = "Interloper FastAPI routes"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -26,6 +26,9 @@ otel = [
26
26
  "opentelemetry-instrumentation-fastapi>=0.65b0",
27
27
  ]
28
28
 
29
+ [project.entry-points."interloper.cli"]
30
+ api = "interloper_api.cli:register"
31
+
29
32
  [build-system]
30
33
  requires = ["uv_build>=0.12.9,<0.13"]
31
34
  build-backend = "uv_build"
@@ -3,7 +3,7 @@
3
3
  # ###############
4
4
  [project]
5
5
  name = "interloper-api"
6
- version = "0.112.0"
6
+ version = "0.113.0"
7
7
  description = "Interloper FastAPI routes"
8
8
  readme = "README.md"
9
9
  authors = [{ name = "Guillaume Onfroy", email = "guillaume@digitlcloud.com" }]
@@ -26,6 +26,9 @@ otel = [
26
26
  "opentelemetry-instrumentation-fastapi>=0.65b0",
27
27
  ]
28
28
 
29
+ [project.entry-points."interloper.cli"]
30
+ api = "interloper_api.cli:register"
31
+
29
32
  [build-system]
30
33
  requires = ["uv_build>=0.12.9,<0.13"]
31
34
  build-backend = "uv_build"
@@ -1,5 +1,6 @@
1
1
  """HTTP API for interloper: catalog, collection, run, and auth endpoints."""
2
2
 
3
3
  from interloper_api.app import create_app
4
+ from interloper_api.server import ApiServer
4
5
 
5
- __all__ = ["create_app"]
6
+ __all__ = ["ApiServer", "create_app"]
@@ -9,17 +9,17 @@ from typing import Any
9
9
  from fastapi import APIRouter, FastAPI, Request
10
10
  from fastapi.middleware.cors import CORSMiddleware
11
11
  from fastapi.responses import JSONResponse
12
- from interloper.catalog.base import Catalog
13
- from interloper.errors import (
12
+ from interloper.catalog import Catalog
13
+ from interloper.errors import ConfigError
14
+ from interloper_db import (
14
15
  ComponentDriftError,
15
- ConfigError,
16
16
  ConflictError,
17
17
  HydrationError,
18
18
  InUseError,
19
19
  NotFoundError,
20
20
  QuotaExceededError,
21
+ Store,
21
22
  )
22
- from interloper_db import Store
23
23
 
24
24
  from interloper_api.dependencies import (
25
25
  set_admin_config,
@@ -0,0 +1,62 @@
1
+ """``interloper api``: serve the HTTP API, the api role's command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import logging
7
+
8
+ from interloper.catalog import Catalog
9
+ from interloper.cli.console import configure_console_logging
10
+ from interloper.settings import AppSettings
11
+ from interloper.utils import Supervisor
12
+ from interloper_db import Store
13
+ from interloper_db.provision import bootstrap
14
+
15
+ from interloper_api.server import ApiServer
16
+
17
+
18
+ def register(subparsers: argparse._SubParsersAction) -> None:
19
+ """Register the ``api`` command.
20
+
21
+ Args:
22
+ subparsers: The root subparsers action to attach to.
23
+ """
24
+ parser = subparsers.add_parser(
25
+ "api",
26
+ help="Serve the HTTP API",
27
+ description="Serve the HTTP API. The frontend is served separately: by nginx in containers, by "
28
+ "`interloper app` locally.",
29
+ )
30
+ parser.add_argument("--host", default=None, help="Bind host (default: server.host, 0.0.0.0)")
31
+ parser.add_argument("--port", type=int, default=None, help="Bind port (default: server.port, 3000)")
32
+ parser.add_argument(
33
+ "--no-create-tables",
34
+ action="store_true",
35
+ default=False,
36
+ help="Skip CREATE TABLE bootstrap on startup (production: run `interloper db init` once instead)",
37
+ )
38
+ parser.set_defaults(handler=_cmd_api)
39
+
40
+
41
+ def _cmd_api(args: argparse.Namespace) -> None:
42
+ """Serve the API until a signal or its death.
43
+
44
+ Args:
45
+ args: Parsed CLI arguments: ``host``, ``port`` and ``no_create_tables``.
46
+ """
47
+ settings = AppSettings.get()
48
+ if args.host is not None:
49
+ settings = settings.model_copy(update={"server": settings.server.model_copy(update={"host": args.host})})
50
+ AppSettings.activate(settings)
51
+ configure_console_logging(
52
+ logging.INFO, "%(asctime)s %(name)s %(levelname)s %(message)s", log_format=settings.log.format
53
+ )
54
+ bootstrap(
55
+ settings.postgres.dsn,
56
+ statement_timeout=settings.postgres.statement_timeout,
57
+ create_tables=not args.no_create_tables,
58
+ )
59
+ catalog = Catalog.from_settings()
60
+ store = Store.from_settings(catalog=catalog)
61
+ port = settings.server.port if args.port is None else args.port
62
+ Supervisor({"api": ApiServer.from_settings(settings, store=store, catalog=catalog, port=port)}).run()
@@ -7,8 +7,7 @@ from typing import Annotated, Any
7
7
  from uuid import UUID
8
8
 
9
9
  from fastapi import Depends, HTTPException
10
- from interloper.errors import NotFoundError
11
- from interloper_db import Profile, Role, Store
10
+ from interloper_db import NotFoundError, Profile, Role, Store
12
11
 
13
12
  from interloper_api.dependencies.auth import get_current_user, get_org_id
14
13
  from interloper_api.dependencies.state import get_store
@@ -10,7 +10,7 @@ from __future__ import annotations
10
10
  from typing import Annotated, Any
11
11
 
12
12
  from fastapi import Depends
13
- from interloper.catalog.base import Catalog
13
+ from interloper.catalog import Catalog
14
14
  from interloper_db import Store
15
15
 
16
16
  _store: Store | None = None
@@ -541,7 +541,7 @@ def _runner_defaults(runner_type: str) -> dict[str, Any]:
541
541
  Allowlisted field names mapped to their default value, empty when the
542
542
  type is unregistered.
543
543
  """
544
- from interloper.runner.base import RUNNERS
544
+ from interloper.runner import RUNNERS
545
545
 
546
546
  runner_cls = RUNNERS.get(runner_type)
547
547
  if runner_cls is None:
@@ -11,8 +11,7 @@ from zoneinfo import ZoneInfo
11
11
  import httpx2
12
12
  from fastapi import APIRouter, Cookie, HTTPException, Response
13
13
  from fastapi.responses import RedirectResponse
14
- from interloper.errors import NotFoundError
15
- from interloper_db import Organisation, Profile, Store
14
+ from interloper_db import NotFoundError, Organisation, Profile, Store
16
15
  from interloper_db.models import AuthSession
17
16
  from pydantic import BaseModel, Field
18
17
 
@@ -15,11 +15,10 @@ from typing import Any, Literal, NoReturn
15
15
  import httpx2
16
16
  import interloper as il
17
17
  from fastapi import APIRouter, Depends, HTTPException
18
- from interloper.connection.base import Connection
18
+ from interloper.connection import Connection
19
19
  from interloper.errors import ConnectionCheckError, format_exception
20
- from interloper.resource.fields import is_fetch_field_provider
21
- from interloper.utils.concurrency import invoke
22
- from interloper.utils.imports import import_from_path
20
+ from interloper.resource import is_fetch_field_provider
21
+ from interloper.utils import import_from_path, invoke
23
22
  from pydantic import BaseModel, Field, ValidationError
24
23
 
25
24
  from interloper_api.dependencies import CatalogDep, EditorDep, require_viewer
@@ -24,8 +24,8 @@ from uuid import UUID
24
24
 
25
25
  from fastapi import APIRouter, HTTPException, Query, Response
26
26
  from interloper.component import KINDS
27
- from interloper.errors import ComponentDriftError, DataNotFoundError
28
- from interloper_db import Component, ComponentQuery, ComponentStatus, DeleteImpact, Page, Store
27
+ from interloper.errors import DataNotFoundError
28
+ from interloper_db import Component, ComponentDriftError, ComponentQuery, ComponentStatus, DeleteImpact, Page, Store
29
29
  from pydantic import BaseModel
30
30
 
31
31
  from interloper_api.dependencies import (
@@ -14,7 +14,7 @@ from typing import Annotated, Literal
14
14
  from uuid import UUID
15
15
 
16
16
  from fastapi import APIRouter, HTTPException, Query
17
- from interloper.utils.time import assume_utc
17
+ from interloper.utils import assume_utc
18
18
  from interloper_db import ComponentStatus
19
19
  from interloper_db.store import insights
20
20
  from pydantic import BaseModel
@@ -13,8 +13,7 @@ from typing import Annotated
13
13
  from uuid import UUID
14
14
 
15
15
  from fastapi import APIRouter, HTTPException, Query, Response
16
- from interloper.errors import NotFoundError
17
- from interloper_db import Page, PageQuery, Role, TokenQuery
16
+ from interloper_db import NotFoundError, Page, PageQuery, Role, TokenQuery
18
17
  from pydantic import BaseModel, Field
19
18
 
20
19
  from interloper_api.dependencies import (
@@ -0,0 +1,72 @@
1
+ """The API as a supervised service: a uvicorn server over the FastAPI app."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING
6
+
7
+ import uvicorn
8
+ from interloper.telemetry import instrument_fastapi
9
+
10
+ from interloper_api.app import create_app
11
+
12
+ if TYPE_CHECKING:
13
+ from fastapi import FastAPI
14
+ from interloper.catalog import Catalog
15
+ from interloper.settings import AppSettings
16
+ from interloper_db import Store
17
+
18
+
19
+ class ApiServer:
20
+ """The HTTP API, served by uvicorn until stopped.
21
+
22
+ Attributes:
23
+ app: The FastAPI application; a composing process may add middleware to it before ``start``.
24
+ """
25
+
26
+ def __init__(self, app: FastAPI, server: uvicorn.Server) -> None:
27
+ """Hold the application and its server.
28
+
29
+ Args:
30
+ app: The FastAPI application.
31
+ server: The uvicorn server bound to it.
32
+ """
33
+ self.app = app
34
+ self._server = server
35
+
36
+ @classmethod
37
+ def from_settings(
38
+ cls,
39
+ settings: AppSettings,
40
+ *,
41
+ store: Store,
42
+ catalog: Catalog,
43
+ port: int,
44
+ cors_origins: list[str] | None = None,
45
+ ) -> ApiServer:
46
+ """Build the API and its server from settings.
47
+
48
+ Args:
49
+ settings: The active settings; ``server.host`` is the bind host.
50
+ store: The store the routes read and write through.
51
+ catalog: The catalog the routes describe.
52
+ port: The port to bind.
53
+ cors_origins: Origins allowed cross-origin requests; ``None`` allows none
54
+ (dev mode passes the Nuxt dev server's origin).
55
+
56
+ Returns:
57
+ The server, not yet started.
58
+ """
59
+ app = create_app(store=store, catalog=catalog, settings=settings, cors_origins=cors_origins)
60
+ instrument_fastapi(app)
61
+ config = uvicorn.Config(
62
+ app, host=settings.server.host, port=port, log_level="info", log_config=None, ws="wsproto"
63
+ )
64
+ return cls(app, uvicorn.Server(config))
65
+
66
+ def start(self) -> None:
67
+ """Serve until stopped."""
68
+ self._server.run()
69
+
70
+ def stop(self) -> None:
71
+ """Ask the server to finish its requests and return."""
72
+ self._server.should_exit = True