flowlab 0.1.0__py3-none-any.whl
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.
- flowlab/__init__.py +109 -0
- flowlab/__main__.py +3 -0
- flowlab/_cache.py +42 -0
- flowlab/_commands.py +101 -0
- flowlab/_console.py +37 -0
- flowlab/_database.py +64 -0
- flowlab/_exceptions.py +31 -0
- flowlab/_flowlab.py +454 -0
- flowlab/_handlers.py +55 -0
- flowlab/_module.py +24 -0
- flowlab/_queue.py +27 -0
- flowlab/_routing.py +46 -0
- flowlab/_settings.py +337 -0
- flowlab/_state.py +32 -0
- flowlab/modules/__init__.py +0 -0
- flowlab/modules/auth/__init__.py +52 -0
- flowlab/modules/auth/_api_keys.py +55 -0
- flowlab/modules/auth/_commands.py +123 -0
- flowlab/modules/auth/_dependencies.py +35 -0
- flowlab/modules/auth/_email.py +2 -0
- flowlab/modules/auth/_mcp.py +69 -0
- flowlab/modules/auth/_models.py +62 -0
- flowlab/modules/auth/_routers.py +92 -0
- flowlab/modules/auth/_schema.py +27 -0
- flowlab/modules/auth/_schemas.py +50 -0
- flowlab/modules/auth/_security.py +97 -0
- flowlab/modules/auth/_users.py +134 -0
- flowlab/modules/auth/migrations/0001_01_01_000000_create_users_table.py +20 -0
- flowlab/modules/auth/migrations/0001_01_01_000001_create_api_keys_table.py +25 -0
- flowlab/modules/cache/__init__.py +3 -0
- flowlab/modules/cache/_cache.py +121 -0
- flowlab/modules/cache/adapters/__init__.py +17 -0
- flowlab/modules/cache/adapters/_base.py +38 -0
- flowlab/modules/cache/adapters/_database.py +107 -0
- flowlab/modules/cache/adapters/_memcached.py +57 -0
- flowlab/modules/cache/adapters/_redis.py +19 -0
- flowlab/modules/cache/adapters/_redis_like.py +55 -0
- flowlab/modules/cache/adapters/_shared_memory.py +85 -0
- flowlab/modules/cache/adapters/_valkey.py +19 -0
- flowlab/modules/db/__init__.py +43 -0
- flowlab/modules/db/_exceptions.py +29 -0
- flowlab/modules/db/_helpers.py +48 -0
- flowlab/modules/db/_json.py +30 -0
- flowlab/modules/db/_query_with_params.py +69 -0
- flowlab/modules/db/_threading.py +295 -0
- flowlab/modules/db/adapters/__init__.py +12 -0
- flowlab/modules/db/adapters/_base.py +161 -0
- flowlab/modules/db/adapters/_mysql.py +269 -0
- flowlab/modules/db/adapters/_postgres.py +610 -0
- flowlab/modules/db/adapters/_sqlite.py +364 -0
- flowlab/modules/db/database/__init__.py +9 -0
- flowlab/modules/db/database/_abc.py +278 -0
- flowlab/modules/db/database/_copy.py +149 -0
- flowlab/modules/db/database/_db.py +202 -0
- flowlab/modules/db/database/_introspection.py +152 -0
- flowlab/modules/db/database/_table.py +210 -0
- flowlab/modules/db/dialects/__init__.py +13 -0
- flowlab/modules/db/dialects/_base.py +213 -0
- flowlab/modules/db/dialects/_mysql.py +306 -0
- flowlab/modules/db/dialects/_postgres.py +273 -0
- flowlab/modules/db/dialects/_sql_dialect.py +1197 -0
- flowlab/modules/db/dialects/_sqlite.py +302 -0
- flowlab/modules/db/orm/__init__.py +52 -0
- flowlab/modules/db/orm/_column.py +30 -0
- flowlab/modules/db/orm/_delete.py +222 -0
- flowlab/modules/db/orm/_insert.py +290 -0
- flowlab/modules/db/orm/_loader.py +168 -0
- flowlab/modules/db/orm/_mapper.py +104 -0
- flowlab/modules/db/orm/_meta.py +192 -0
- flowlab/modules/db/orm/_model.py +9 -0
- flowlab/modules/db/orm/_relation.py +212 -0
- flowlab/modules/db/orm/_select.py +50 -0
- flowlab/modules/db/orm/_tree.py +75 -0
- flowlab/modules/db/orm/_update.py +125 -0
- flowlab/modules/db/orm/enums/__init__.py +5 -0
- flowlab/modules/db/orm/enums/_relation.py +8 -0
- flowlab/modules/db/query/__init__.py +41 -0
- flowlab/modules/db/query/_alter_table.py +28 -0
- flowlab/modules/db/query/_condition.py +20 -0
- flowlab/modules/db/query/_condition_group.py +24 -0
- flowlab/modules/db/query/_condition_mixin.py +370 -0
- flowlab/modules/db/query/_create_index.py +61 -0
- flowlab/modules/db/query/_create_table.py +39 -0
- flowlab/modules/db/query/_ddl_mixins.py +310 -0
- flowlab/modules/db/query/_delete.py +31 -0
- flowlab/modules/db/query/_drop_index.py +43 -0
- flowlab/modules/db/query/_drop_table.py +31 -0
- flowlab/modules/db/query/_having_mixin.py +286 -0
- flowlab/modules/db/query/_insert.py +235 -0
- flowlab/modules/db/query/_join.py +293 -0
- flowlab/modules/db/query/_joins_mixin.py +142 -0
- flowlab/modules/db/query/_on_conflict.py +10 -0
- flowlab/modules/db/query/_order_by.py +15 -0
- flowlab/modules/db/query/_query.py +77 -0
- flowlab/modules/db/query/_select.py +79 -0
- flowlab/modules/db/query/_simple_mixins.py +159 -0
- flowlab/modules/db/query/_union.py +15 -0
- flowlab/modules/db/query/_update.py +29 -0
- flowlab/modules/db/query/_where_mixin.py +286 -0
- flowlab/modules/db/query/ddl/__init__.py +38 -0
- flowlab/modules/db/query/ddl/_add_column.py +11 -0
- flowlab/modules/db/query/ddl/_add_foreign_key_constraint.py +11 -0
- flowlab/modules/db/query/ddl/_add_primary_keys.py +10 -0
- flowlab/modules/db/query/ddl/_add_unique_constraint.py +11 -0
- flowlab/modules/db/query/ddl/_alter.py +7 -0
- flowlab/modules/db/query/ddl/_alter_column.py +11 -0
- flowlab/modules/db/query/ddl/_column.py +16 -0
- flowlab/modules/db/query/ddl/_constraint.py +7 -0
- flowlab/modules/db/query/ddl/_drop_column.py +10 -0
- flowlab/modules/db/query/ddl/_drop_constraint.py +10 -0
- flowlab/modules/db/query/ddl/_foreign_key_constraint.py +16 -0
- flowlab/modules/db/query/ddl/_index.py +10 -0
- flowlab/modules/db/query/ddl/_raw_alter.py +10 -0
- flowlab/modules/db/query/ddl/_raw_constraint.py +10 -0
- flowlab/modules/db/query/ddl/_rename_column.py +11 -0
- flowlab/modules/db/query/ddl/_table_description.py +91 -0
- flowlab/modules/db/query/ddl/_unique_constraint.py +11 -0
- flowlab/modules/db/query/enums/__init__.py +17 -0
- flowlab/modules/db/query/enums/_chain.py +6 -0
- flowlab/modules/db/query/enums/_condition.py +23 -0
- flowlab/modules/db/query/enums/_join.py +10 -0
- flowlab/modules/db/query/enums/_order_by_dir.py +6 -0
- flowlab/modules/db/query/enums/_referential_action.py +32 -0
- flowlab/modules/db/query/enums/_type.py +10 -0
- flowlab/modules/db/query/enums/_union.py +6 -0
- flowlab/modules/db/query/expressions/__init__.py +22 -0
- flowlab/modules/db/query/expressions/_alias.py +41 -0
- flowlab/modules/db/query/expressions/_current_timestamp.py +19 -0
- flowlab/modules/db/query/expressions/_excluded.py +13 -0
- flowlab/modules/db/query/expressions/_expression.py +26 -0
- flowlab/modules/db/query/expressions/_identifier.py +22 -0
- flowlab/modules/db/query/expressions/_postgres_array.py +33 -0
- flowlab/modules/db/query/expressions/_raw.py +22 -0
- flowlab/modules/db/query/expressions/_sql.py +18 -0
- flowlab/modules/db/query/expressions/_sub_query.py +26 -0
- flowlab/modules/db/result/__init__.py +15 -0
- flowlab/modules/db/result/_base.py +50 -0
- flowlab/modules/db/result/_mysql.py +107 -0
- flowlab/modules/db/result/_postgres.py +178 -0
- flowlab/modules/db/result/_result.py +30 -0
- flowlab/modules/db/result/_sqlite.py +65 -0
- flowlab/modules/migrator/__init__.py +12 -0
- flowlab/modules/migrator/_loader.py +52 -0
- flowlab/modules/migrator/_migration_abc.py +20 -0
- flowlab/modules/migrator/_migrator.py +108 -0
- flowlab/modules/migrator/_schema.py +72 -0
- flowlab/modules/migrator/_template.py +35 -0
- flowlab/modules/queue/__init__.py +29 -0
- flowlab/modules/queue/_commands.py +100 -0
- flowlab/modules/queue/_job.py +134 -0
- flowlab/modules/queue/_queue.py +138 -0
- flowlab/modules/queue/_schema.py +28 -0
- flowlab/modules/queue/_worker.py +126 -0
- flowlab/modules/queue/adapters/__init__.py +17 -0
- flowlab/modules/queue/adapters/_base.py +70 -0
- flowlab/modules/queue/adapters/_database.py +164 -0
- flowlab/modules/queue/adapters/_redis.py +20 -0
- flowlab/modules/queue/adapters/_redis_like.py +164 -0
- flowlab/modules/queue/adapters/_sync.py +43 -0
- flowlab/modules/queue/adapters/_valkey.py +20 -0
- flowlab/modules/queue/migrations/0001_01_01_000002_create_jobs_table.py +41 -0
- flowlab/modules/ratelimit/__init__.py +15 -0
- flowlab/modules/ratelimit/_http.py +62 -0
- flowlab/modules/ratelimit/_limiter.py +69 -0
- flowlab/modules/ratelimit/_limits.py +44 -0
- flowlab/modules/ratelimit/_mcp.py +64 -0
- flowlab/py.typed +0 -0
- flowlab-0.1.0.dist-info/METADATA +233 -0
- flowlab-0.1.0.dist-info/RECORD +173 -0
- flowlab-0.1.0.dist-info/WHEEL +5 -0
- flowlab-0.1.0.dist-info/entry_points.txt +2 -0
- flowlab-0.1.0.dist-info/licenses/LICENSE +21 -0
- flowlab-0.1.0.dist-info/top_level.txt +1 -0
flowlab/__init__.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
2
|
+
|
|
3
|
+
from fastapi import (
|
|
4
|
+
APIRouter,
|
|
5
|
+
BackgroundTasks,
|
|
6
|
+
Body,
|
|
7
|
+
Cookie,
|
|
8
|
+
Depends,
|
|
9
|
+
File,
|
|
10
|
+
Form,
|
|
11
|
+
Header,
|
|
12
|
+
HTTPException,
|
|
13
|
+
Path,
|
|
14
|
+
Query,
|
|
15
|
+
Request,
|
|
16
|
+
Response,
|
|
17
|
+
Security,
|
|
18
|
+
UploadFile,
|
|
19
|
+
WebSocket,
|
|
20
|
+
WebSocketDisconnect,
|
|
21
|
+
status,
|
|
22
|
+
)
|
|
23
|
+
from fastmcp.dependencies import Depends as MCPDepends
|
|
24
|
+
|
|
25
|
+
from flowlab._cache import Cache, get_cache
|
|
26
|
+
from flowlab._database import DB, INTEGRITY_ERRORS, get_db
|
|
27
|
+
from flowlab._exceptions import (
|
|
28
|
+
AuthNotEnabled,
|
|
29
|
+
FlowLabAlreadyInitialized,
|
|
30
|
+
FlowLabError,
|
|
31
|
+
FlowLabNotInitialized,
|
|
32
|
+
NotConnected,
|
|
33
|
+
SchemaMismatch,
|
|
34
|
+
)
|
|
35
|
+
from flowlab._flowlab import FlowLab
|
|
36
|
+
from flowlab._module import Module
|
|
37
|
+
from flowlab._queue import get_queue
|
|
38
|
+
from flowlab._routing import Command, CommandRouter, MCPRouter
|
|
39
|
+
from flowlab._settings import (
|
|
40
|
+
AppEnv,
|
|
41
|
+
AuthSettings,
|
|
42
|
+
CacheDriver,
|
|
43
|
+
CacheSettings,
|
|
44
|
+
DatabaseSettings,
|
|
45
|
+
DBDriver,
|
|
46
|
+
FastAPISettings,
|
|
47
|
+
FastMCPSettings,
|
|
48
|
+
QueueDriver,
|
|
49
|
+
QueueSettings,
|
|
50
|
+
TyperSettings,
|
|
51
|
+
)
|
|
52
|
+
from flowlab._state import get_app
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
__version__ = version("flowlab")
|
|
56
|
+
except PackageNotFoundError: # pragma: no cover - running from a source tree without metadata
|
|
57
|
+
__version__ = "0.0.0"
|
|
58
|
+
|
|
59
|
+
__all__ = [
|
|
60
|
+
"DB",
|
|
61
|
+
"INTEGRITY_ERRORS",
|
|
62
|
+
"APIRouter",
|
|
63
|
+
"AppEnv",
|
|
64
|
+
"AuthNotEnabled",
|
|
65
|
+
"AuthSettings",
|
|
66
|
+
"BackgroundTasks",
|
|
67
|
+
"Body",
|
|
68
|
+
"Cache",
|
|
69
|
+
"CacheDriver",
|
|
70
|
+
"CacheSettings",
|
|
71
|
+
"Command",
|
|
72
|
+
"CommandRouter",
|
|
73
|
+
"Cookie",
|
|
74
|
+
"DBDriver",
|
|
75
|
+
"DatabaseSettings",
|
|
76
|
+
"Depends",
|
|
77
|
+
"FastAPISettings",
|
|
78
|
+
"FastMCPSettings",
|
|
79
|
+
"File",
|
|
80
|
+
"FlowLab",
|
|
81
|
+
"FlowLabAlreadyInitialized",
|
|
82
|
+
"FlowLabError",
|
|
83
|
+
"FlowLabNotInitialized",
|
|
84
|
+
"Form",
|
|
85
|
+
"HTTPException",
|
|
86
|
+
"Header",
|
|
87
|
+
"MCPDepends",
|
|
88
|
+
"MCPRouter",
|
|
89
|
+
"Module",
|
|
90
|
+
"NotConnected",
|
|
91
|
+
"Path",
|
|
92
|
+
"Query",
|
|
93
|
+
"QueueDriver",
|
|
94
|
+
"QueueSettings",
|
|
95
|
+
"Request",
|
|
96
|
+
"Response",
|
|
97
|
+
"SchemaMismatch",
|
|
98
|
+
"Security",
|
|
99
|
+
"TyperSettings",
|
|
100
|
+
"UploadFile",
|
|
101
|
+
"WebSocket",
|
|
102
|
+
"WebSocketDisconnect",
|
|
103
|
+
"__version__",
|
|
104
|
+
"get_app",
|
|
105
|
+
"get_cache",
|
|
106
|
+
"get_db",
|
|
107
|
+
"get_queue",
|
|
108
|
+
"status",
|
|
109
|
+
]
|
flowlab/__main__.py
ADDED
flowlab/_cache.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
2
|
+
from typing import Annotated
|
|
3
|
+
|
|
4
|
+
from fastapi import Depends
|
|
5
|
+
|
|
6
|
+
from flowlab._settings import CacheDriver, CacheSettings
|
|
7
|
+
from flowlab._state import get_app
|
|
8
|
+
from flowlab.modules.cache import Cache as CacheStore
|
|
9
|
+
from flowlab.modules.db.database import DB as Database
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def connect_cache_store(settings: CacheSettings, db: Database) -> CacheStore:
|
|
13
|
+
driver = settings.cache_driver
|
|
14
|
+
options = settings.cache_options()
|
|
15
|
+
|
|
16
|
+
if driver is CacheDriver.MEMORY:
|
|
17
|
+
return CacheStore.shared_memory()
|
|
18
|
+
|
|
19
|
+
if driver is CacheDriver.DATABASE:
|
|
20
|
+
return CacheStore.connect_database(db, **options)
|
|
21
|
+
|
|
22
|
+
if driver is CacheDriver.REDIS:
|
|
23
|
+
return CacheStore.connect_redis(**options)
|
|
24
|
+
|
|
25
|
+
if driver is CacheDriver.VALKEY:
|
|
26
|
+
return CacheStore.connect_valkey(**options)
|
|
27
|
+
|
|
28
|
+
if driver is CacheDriver.MEMCACHED:
|
|
29
|
+
return CacheStore.connect_memcached(**options)
|
|
30
|
+
|
|
31
|
+
raise ValueError(f"Unsupported CACHE_DRIVER: {driver!r}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_cache() -> CacheStore:
|
|
35
|
+
return get_app().cache
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def connect_cache() -> Generator[CacheStore, None, None]:
|
|
39
|
+
yield get_cache()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
Cache = Annotated[CacheStore, Depends(connect_cache)]
|
flowlab/_commands.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
import uvicorn
|
|
8
|
+
|
|
9
|
+
from flowlab._routing import CommandRouter
|
|
10
|
+
from flowlab._state import get_app
|
|
11
|
+
from flowlab.modules.migrator import drop_all_tables
|
|
12
|
+
from flowlab.modules.migrator._schema import scaffold
|
|
13
|
+
|
|
14
|
+
RELOAD_TARGET_ENV = "FLOWLAB_RELOAD_TARGET"
|
|
15
|
+
|
|
16
|
+
commands = CommandRouter()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@commands.command("server:start", lifespan=False)
|
|
20
|
+
def server_start(
|
|
21
|
+
host: Annotated[str | None, typer.Option(help="Interface to bind. Defaults to SERVER_HOST.")] = None,
|
|
22
|
+
port: Annotated[int | None, typer.Option(help="Port to bind. Defaults to SERVER_PORT.")] = None,
|
|
23
|
+
reload: Annotated[
|
|
24
|
+
bool | None, typer.Option(help="Restart on code changes. Defaults to on when APP_ENV=local.")
|
|
25
|
+
] = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
app = get_app()
|
|
28
|
+
settings = app.fastapi_settings
|
|
29
|
+
host = settings.server_host if host is None else host
|
|
30
|
+
port = settings.server_port if port is None else port
|
|
31
|
+
reload = settings.is_local if reload is None else reload
|
|
32
|
+
|
|
33
|
+
if not reload:
|
|
34
|
+
uvicorn.run(app, host=host, port=port)
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
os.environ[RELOAD_TARGET_ENV] = app._import_string()
|
|
38
|
+
|
|
39
|
+
uvicorn.run(
|
|
40
|
+
"flowlab._console:reload_target",
|
|
41
|
+
factory=True,
|
|
42
|
+
host=host,
|
|
43
|
+
port=port,
|
|
44
|
+
reload=True,
|
|
45
|
+
app_dir=str(app.project_dir),
|
|
46
|
+
reload_dirs=[str(app.project_dir)],
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@commands.command("migrations:init")
|
|
51
|
+
def migrations_init() -> None:
|
|
52
|
+
_ = get_app().migrator
|
|
53
|
+
typer.echo("Migrations table is ready.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@commands.command("migrations:up")
|
|
57
|
+
def migrations_up() -> None:
|
|
58
|
+
get_app().migrator.up()
|
|
59
|
+
typer.echo("Migrated.")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@commands.command("migrations:down")
|
|
63
|
+
def migrations_down() -> None:
|
|
64
|
+
get_app().migrator.down()
|
|
65
|
+
typer.echo("Rolled back the last batch.")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@commands.command("migrations:fresh")
|
|
69
|
+
def migrations_fresh(
|
|
70
|
+
force: Annotated[bool, typer.Option("--force", help="Required when APP_ENV is production.")] = False,
|
|
71
|
+
) -> None:
|
|
72
|
+
app = get_app()
|
|
73
|
+
|
|
74
|
+
if app.fastapi_settings.is_production and not force:
|
|
75
|
+
typer.echo("Refusing to drop every table in production without --force.", err=True)
|
|
76
|
+
raise typer.Exit(1)
|
|
77
|
+
|
|
78
|
+
for table in drop_all_tables(app.db):
|
|
79
|
+
typer.echo(f"Dropped {table}")
|
|
80
|
+
|
|
81
|
+
app.migrator.up()
|
|
82
|
+
typer.echo("Migrated.")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@commands.command("migrations:make")
|
|
86
|
+
def migrations_make(
|
|
87
|
+
name: Annotated[str, typer.Argument(help="snake_case description, e.g. create_posts_table")],
|
|
88
|
+
) -> None:
|
|
89
|
+
app = get_app()
|
|
90
|
+
typer.echo(scaffold(app.db, app.migrations_dir, name))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@commands.command(
|
|
94
|
+
"test",
|
|
95
|
+
lifespan=False,
|
|
96
|
+
context_settings={"allow_extra_args": True, "ignore_unknown_options": True},
|
|
97
|
+
)
|
|
98
|
+
def run_tests(ctx: typer.Context) -> None:
|
|
99
|
+
result = subprocess.run([sys.executable, "-m", "pytest", *ctx.args], cwd=get_app().project_dir, check=False)
|
|
100
|
+
|
|
101
|
+
raise typer.Exit(result.returncode)
|
flowlab/_console.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from flowlab._commands import RELOAD_TARGET_ENV
|
|
6
|
+
from flowlab._flowlab import FlowLab
|
|
7
|
+
|
|
8
|
+
DEFAULT_APP = "main:app"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def load_app(target: str) -> FlowLab:
|
|
12
|
+
module_name, _, attribute = target.partition(":")
|
|
13
|
+
sys.path.insert(0, os.getcwd())
|
|
14
|
+
|
|
15
|
+
app = getattr(importlib.import_module(module_name), attribute or "app")
|
|
16
|
+
|
|
17
|
+
if not isinstance(app, FlowLab):
|
|
18
|
+
raise SystemExit(f"{target} is not a FlowLab application")
|
|
19
|
+
|
|
20
|
+
return app
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def reload_target() -> FlowLab:
|
|
24
|
+
if FlowLab.initialized():
|
|
25
|
+
return FlowLab.instance()
|
|
26
|
+
|
|
27
|
+
return load_app(os.environ[RELOAD_TARGET_ENV])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def main() -> None:
|
|
31
|
+
args = sys.argv[1:]
|
|
32
|
+
target = os.environ.get("FLOWLAB_APP", DEFAULT_APP)
|
|
33
|
+
|
|
34
|
+
if len(args) >= 2 and args[0] == "--app":
|
|
35
|
+
target, args = args[1], args[2:]
|
|
36
|
+
|
|
37
|
+
load_app(target).run(args)
|
flowlab/_database.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import importlib
|
|
3
|
+
import sqlite3
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
|
|
8
|
+
from fastapi import Depends
|
|
9
|
+
|
|
10
|
+
from flowlab._settings import DatabaseSettings, DBDriver
|
|
11
|
+
from flowlab._state import get_app
|
|
12
|
+
from flowlab.modules.db.database import DB as Database
|
|
13
|
+
|
|
14
|
+
DB_API_DRIVER_MODULES = ("psycopg", "mysql.connector")
|
|
15
|
+
|
|
16
|
+
ASYNCPG_INTEGRITY_ERROR = ("asyncpg.exceptions", "IntegrityConstraintViolationError")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _integrity_errors() -> tuple[type[BaseException], ...]:
|
|
20
|
+
errors: list[type[BaseException]] = [sqlite3.IntegrityError]
|
|
21
|
+
|
|
22
|
+
for module_name in DB_API_DRIVER_MODULES:
|
|
23
|
+
with contextlib.suppress(ImportError):
|
|
24
|
+
errors.append(importlib.import_module(module_name).IntegrityError)
|
|
25
|
+
|
|
26
|
+
module_name, attribute = ASYNCPG_INTEGRITY_ERROR
|
|
27
|
+
|
|
28
|
+
with contextlib.suppress(ImportError, AttributeError):
|
|
29
|
+
errors.append(getattr(importlib.import_module(module_name), attribute))
|
|
30
|
+
|
|
31
|
+
return tuple(errors)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
INTEGRITY_ERRORS = _integrity_errors()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def connect_database(settings: DatabaseSettings, project_dir: Path) -> Database:
|
|
38
|
+
driver = settings.db_driver
|
|
39
|
+
options = settings.db_options(project_dir)
|
|
40
|
+
|
|
41
|
+
if driver is DBDriver.SQLITE:
|
|
42
|
+
return Database.connect_sqlite(**options)
|
|
43
|
+
|
|
44
|
+
if driver is DBDriver.POSTGRESQL:
|
|
45
|
+
return Database.connect_postgresql(**options)
|
|
46
|
+
|
|
47
|
+
if driver is DBDriver.MYSQL:
|
|
48
|
+
return Database.connect_mysql(**options)
|
|
49
|
+
|
|
50
|
+
if driver is DBDriver.MARIADB:
|
|
51
|
+
return Database.connect_mariadb(**options)
|
|
52
|
+
|
|
53
|
+
raise ValueError(f"Unsupported DB_DRIVER: {driver!r}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_db() -> Database:
|
|
57
|
+
return get_app().db
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def connect() -> Generator[Database, None, None]:
|
|
61
|
+
yield get_db()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
DB = Annotated[Database, Depends(connect)]
|
flowlab/_exceptions.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class FlowLabError(RuntimeError):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class FlowLabNotInitialized(FlowLabError):
|
|
6
|
+
def __init__(self) -> None:
|
|
7
|
+
super().__init__("No FlowLab application has been initialized; create one with FlowLab(...) first")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FlowLabAlreadyInitialized(FlowLabError):
|
|
11
|
+
def __init__(self) -> None:
|
|
12
|
+
super().__init__("A FlowLab application already exists; use FlowLab.instance() to reach it")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class NotConnected(FlowLabError):
|
|
16
|
+
def __init__(self, resource: str) -> None:
|
|
17
|
+
super().__init__(f"The {resource} is not connected; use it inside app.lifespan() or a running app")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AuthNotEnabled(FlowLabError):
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
super().__init__("Auth is not enabled; pass auth_settings=AuthSettings() to FlowLab(...)")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SchemaMismatch(FlowLabError):
|
|
26
|
+
def __init__(self, module: str, missing: list[str]) -> None:
|
|
27
|
+
self.missing = missing
|
|
28
|
+
super().__init__(
|
|
29
|
+
f"The {module} module needs {', '.join(missing)}. "
|
|
30
|
+
f"Run `{module}:install` to add its migrations, then `migrations:up`"
|
|
31
|
+
)
|