auto-rest-api 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.
Potentially problematic release.
This version of auto-rest-api might be problematic. Click here for more details.
- auto_rest/__init__.py +3 -0
- auto_rest/__main__.py +93 -0
- auto_rest/cli.py +127 -0
- auto_rest/handlers.py +362 -0
- auto_rest/models.py +228 -0
- auto_rest/params.py +147 -0
- auto_rest/queries.py +107 -0
- auto_rest/routers.py +168 -0
- auto_rest_api-0.1.0.dist-info/LICENSE.md +675 -0
- auto_rest_api-0.1.0.dist-info/METADATA +86 -0
- auto_rest_api-0.1.0.dist-info/RECORD +13 -0
- auto_rest_api-0.1.0.dist-info/WHEEL +4 -0
- auto_rest_api-0.1.0.dist-info/entry_points.txt +3 -0
auto_rest/__init__.py
ADDED
auto_rest/__main__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Application entrypoint triggered by calling the packaged CLI command."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import uvicorn
|
|
7
|
+
import yaml
|
|
8
|
+
from fastapi import FastAPI
|
|
9
|
+
|
|
10
|
+
from .cli import *
|
|
11
|
+
from .models import *
|
|
12
|
+
from .routers import *
|
|
13
|
+
|
|
14
|
+
__all__ = ["main", "run_application"]
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main() -> None: # pragma: no cover
|
|
20
|
+
"""Parse command-line arguments and launch an API server."""
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
parser = create_cli_parser()
|
|
24
|
+
args = vars(parser.parse_args())
|
|
25
|
+
log_level = args.pop("log_level")
|
|
26
|
+
|
|
27
|
+
configure_cli_logging(log_level)
|
|
28
|
+
run_application(**args)
|
|
29
|
+
|
|
30
|
+
except KeyboardInterrupt:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logger.critical(str(e), exc_info=True)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def run_application(
|
|
38
|
+
enable_docs: bool,
|
|
39
|
+
enable_write: bool,
|
|
40
|
+
db_driver: str,
|
|
41
|
+
db_host: str,
|
|
42
|
+
db_port: int,
|
|
43
|
+
db_name: str,
|
|
44
|
+
db_user: str,
|
|
45
|
+
db_pass: str,
|
|
46
|
+
db_config: Path | None,
|
|
47
|
+
server_host: str,
|
|
48
|
+
server_port: int,
|
|
49
|
+
app_title: str,
|
|
50
|
+
app_version: str,
|
|
51
|
+
) -> None: # pragma: no cover
|
|
52
|
+
"""Run an Auto-REST API server.
|
|
53
|
+
|
|
54
|
+
This function is equivalent to launching an API server from the command line
|
|
55
|
+
and accepts the same arguments as those provided in the CLI.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
enable_docs: Whether to enable the 'docs' API endpoint.
|
|
59
|
+
enable_write: Whether to enable support for write operations.
|
|
60
|
+
db_driver: SQLAlchemy-compatible database driver.
|
|
61
|
+
db_host: Database host address.
|
|
62
|
+
db_port: Database port number.
|
|
63
|
+
db_name: Database name.
|
|
64
|
+
db_user: Database authentication username.
|
|
65
|
+
db_pass: Database authentication password.
|
|
66
|
+
db_config: Path to a database configuration file.
|
|
67
|
+
server_host: API server host address.
|
|
68
|
+
server_port: API server port number.
|
|
69
|
+
app_title: title for the generated OpenAPI schema.
|
|
70
|
+
app_version: version number for the generated OpenAPI schema.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Connect to and map the database.
|
|
74
|
+
logger.info(f"Mapping database schema for {db_name}.")
|
|
75
|
+
db_url = create_db_url(driver=db_driver, host=db_host, port=db_port, database=db_name, username=db_user, password=db_pass)
|
|
76
|
+
db_kwargs = yaml.safe_load(db_config.read_text()) if db_config else {}
|
|
77
|
+
db_conn = create_db_engine(db_url, **db_kwargs)
|
|
78
|
+
db_meta = create_db_metadata(db_conn)
|
|
79
|
+
db_models = create_db_models(db_meta)
|
|
80
|
+
|
|
81
|
+
# Build an empty application and dynamically add the requested functionality.
|
|
82
|
+
logger.info("Creating API application.")
|
|
83
|
+
app = FastAPI(title=app_title, version=app_version, docs_url="/docs/" if enable_docs else None, redoc_url=None)
|
|
84
|
+
app.include_router(create_welcome_router(), prefix="")
|
|
85
|
+
app.include_router(create_meta_router(db_conn, db_meta, app_title, app_version), prefix="/meta")
|
|
86
|
+
|
|
87
|
+
for model_name, model in db_models.items():
|
|
88
|
+
logger.info(f"Adding `/db/{model_name}` endpoint.")
|
|
89
|
+
app.include_router(create_model_router(db_conn, model, enable_write), prefix=f"/db/{model_name}")
|
|
90
|
+
|
|
91
|
+
# Launch the API server.
|
|
92
|
+
logger.info(f"Launching API server on http://{server_host}:{server_port}.")
|
|
93
|
+
uvicorn.run(app, host=server_host, port=server_port, log_level="error")
|
auto_rest/cli.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The `cli` module manages input/output operations for the application's
|
|
3
|
+
command line interface (CLI). Application inputs are parsed using the
|
|
4
|
+
built-in `argparse` module while output messages are handled using the
|
|
5
|
+
Python `logging` library.
|
|
6
|
+
|
|
7
|
+
!!! example "Example: Parsing Arguments"
|
|
8
|
+
|
|
9
|
+
The `create_argument_parser` function returns an `ArgumentParser`
|
|
10
|
+
instance with pre-populated argument definitions.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from auto_rest.cli import create_argument_parser
|
|
14
|
+
|
|
15
|
+
parser = create_argument_parser()
|
|
16
|
+
args = parser.parse_args()
|
|
17
|
+
print(vars(args))
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
!!! example "Example: Enabling Console Logging"
|
|
21
|
+
|
|
22
|
+
The `configure_cli_logging` method overrides any existing logging
|
|
23
|
+
configurations and enables console logging according to the provided log
|
|
24
|
+
level.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from auto_rest.cli import configure_cli_logging
|
|
28
|
+
|
|
29
|
+
configure_cli_logging(log_level="INFO")
|
|
30
|
+
```
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import importlib.metadata
|
|
34
|
+
import logging
|
|
35
|
+
from argparse import ArgumentParser, HelpFormatter
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
from uvicorn.logging import DefaultFormatter
|
|
39
|
+
|
|
40
|
+
__all__ = ["VERSION", "configure_cli_logging", "create_cli_parser"]
|
|
41
|
+
|
|
42
|
+
VERSION = importlib.metadata.version("auto-rest-api")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def configure_cli_logging(level: str) -> None:
|
|
46
|
+
"""Enable console logging with the specified application log level.
|
|
47
|
+
|
|
48
|
+
Calling this method overrides and removes all previously configured
|
|
49
|
+
logging configurations.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
level: The Python logging level.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
# Normalize and validate the logging level.
|
|
56
|
+
level = level.upper()
|
|
57
|
+
if level not in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"):
|
|
58
|
+
raise ValueError(f"Invalid logging level: {level}")
|
|
59
|
+
|
|
60
|
+
# Set up logging with a stream handler.
|
|
61
|
+
handler = logging.StreamHandler()
|
|
62
|
+
handler.setFormatter(DefaultFormatter(fmt="%(levelprefix)s %(message)s"))
|
|
63
|
+
logging.basicConfig(
|
|
64
|
+
force=True,
|
|
65
|
+
level=level,
|
|
66
|
+
format="%(levelprefix)s %(message)s",
|
|
67
|
+
handlers=[handler],
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def create_cli_parser(exit_on_error: bool = True) -> ArgumentParser:
|
|
72
|
+
"""Create a command-line argument parser with preconfigured arguments.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
exit_on_error: Whether to exit the program on a parsing error.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
An argument parser instance.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
formatter = lambda prog: HelpFormatter(prog, max_help_position=29)
|
|
82
|
+
parser = ArgumentParser(
|
|
83
|
+
prog="auto-rest",
|
|
84
|
+
description="Automatically map database schemas and deploy per-table REST API endpoints.",
|
|
85
|
+
exit_on_error=exit_on_error,
|
|
86
|
+
formatter_class=formatter
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
parser.add_argument("--version", action="version", version=VERSION)
|
|
90
|
+
parser.add_argument(
|
|
91
|
+
"--log-level",
|
|
92
|
+
default="INFO",
|
|
93
|
+
type=lambda x: x.upper(),
|
|
94
|
+
choices=["DEBUG", "INFO", "WARNING", "ERROR"],
|
|
95
|
+
help="Set the logging level."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
features = parser.add_argument_group(title="API features")
|
|
99
|
+
features.add_argument("--enable-docs", action="store_true", help="enable the 'docs' endpoint.")
|
|
100
|
+
features.add_argument("--enable-write", action="store_true", help="enable support for write operations.")
|
|
101
|
+
|
|
102
|
+
driver = parser.add_argument_group("database type")
|
|
103
|
+
db_type = driver.add_mutually_exclusive_group(required=True)
|
|
104
|
+
db_type.add_argument("--sqlite", action="store_const", dest="db_driver", const="sqlite+aiosqlite", help="use a SQLite database driver.")
|
|
105
|
+
db_type.add_argument("--psql", action="store_const", dest="db_driver", const="postgresql+asyncpg", help="use a PostgreSQL database driver.")
|
|
106
|
+
db_type.add_argument("--mysql", action="store_const", dest="db_driver", const="mysql+asyncmy", help="use a MySQL database driver.")
|
|
107
|
+
db_type.add_argument("--oracle", action="store_const", dest="db_driver", const="oracle+oracledb", help="use an Oracle database driver.")
|
|
108
|
+
db_type.add_argument("--mssql", action="store_const", dest="db_driver", const="mssql+aiomysql", help="use a Microsoft database driver.")
|
|
109
|
+
db_type.add_argument("--driver", action="store", dest="db_driver", help="use a custom database driver.")
|
|
110
|
+
|
|
111
|
+
database = parser.add_argument_group("database settings")
|
|
112
|
+
database.add_argument("--db-host", help="database address to connect to.")
|
|
113
|
+
database.add_argument("--db-port", type=int, help="database port to connect to.")
|
|
114
|
+
database.add_argument("--db-name", required=True, help="database name or file path to connect to.")
|
|
115
|
+
database.add_argument("--db-user", help="username to authenticate with.")
|
|
116
|
+
database.add_argument("--db-pass", help="password to authenticate with.")
|
|
117
|
+
database.add_argument("--db-config", action="store", type=Path, help="path to a database configuration file.")
|
|
118
|
+
|
|
119
|
+
server = parser.add_argument_group(title="server settings")
|
|
120
|
+
server.add_argument("--server-host", default="127.0.0.1", help="API server host address.")
|
|
121
|
+
server.add_argument("--server-port", type=int, default=8081, help="API server port number.")
|
|
122
|
+
|
|
123
|
+
schema = parser.add_argument_group(title="application settings")
|
|
124
|
+
schema.add_argument("--app-title", default="Auto-REST", help="application title.")
|
|
125
|
+
schema.add_argument("--app-version", default=VERSION, help="application version number.")
|
|
126
|
+
|
|
127
|
+
return parser
|
auto_rest/handlers.py
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
"""
|
|
2
|
+
An **endpoint handler** is a function designed to process incoming HTTP
|
|
3
|
+
requests for single API endpoint. In `auto_rest`, handlers are
|
|
4
|
+
created dynamically using a factory pattern. This approach allows
|
|
5
|
+
handler logic to be customized and reused across multiple endpoints.
|
|
6
|
+
|
|
7
|
+
!!! example "Example: Creating a Handler"
|
|
8
|
+
|
|
9
|
+
New endpoint handlers are created dynamically using factory methods.
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
welcome_handler = create_welcome_handler()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Handler functions are defined as asynchronous coroutines.
|
|
16
|
+
This provides improved performance when handling large numbers of
|
|
17
|
+
incoming requests.
|
|
18
|
+
|
|
19
|
+
!!! example "Example: Async Handlers"
|
|
20
|
+
|
|
21
|
+
Python requires asynchronous coroutines to be run from an asynchronous
|
|
22
|
+
context. In the following example, this is achieved using `asyncio.run`.
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
import asyncio
|
|
26
|
+
|
|
27
|
+
return_value = asyncio.run(welcome_handler())
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Handlers are specifically designed to integrate with the FastAPI framework,
|
|
31
|
+
including support for FastAPI's type hinting and data validation capabilities.
|
|
32
|
+
This makes it easy to incorporate handlers into a FastAPI application.
|
|
33
|
+
|
|
34
|
+
!!! example "Example: Adding a Handler to an Application"
|
|
35
|
+
|
|
36
|
+
Use the `add_api_route` method to dynamically add handler functions to
|
|
37
|
+
an existing application instance.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
app = FastAPI(...)
|
|
41
|
+
|
|
42
|
+
handler = create_welcome_handler()
|
|
43
|
+
app.add_api_route("/", handler, methods=["GET"], ...)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
!!! info "Developer Note"
|
|
47
|
+
|
|
48
|
+
FastAPI internally performs post-processing on values returned by endpoint
|
|
49
|
+
handlers before sending them in an HTTP response. For this reason, handlers
|
|
50
|
+
should always be tested within the context of a FastAPI application.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
import logging
|
|
54
|
+
from typing import Awaitable, Callable
|
|
55
|
+
|
|
56
|
+
from fastapi import Depends, Response
|
|
57
|
+
from pydantic import create_model
|
|
58
|
+
from pydantic.main import ModelT
|
|
59
|
+
from sqlalchemy import insert, MetaData, select
|
|
60
|
+
from starlette.requests import Request
|
|
61
|
+
|
|
62
|
+
from .models import *
|
|
63
|
+
from .params import *
|
|
64
|
+
from .queries import *
|
|
65
|
+
|
|
66
|
+
__all__ = [
|
|
67
|
+
"create_about_handler",
|
|
68
|
+
"create_delete_record_handler",
|
|
69
|
+
"create_engine_handler",
|
|
70
|
+
"create_get_record_handler",
|
|
71
|
+
"create_list_records_handler",
|
|
72
|
+
"create_patch_record_handler",
|
|
73
|
+
"create_post_record_handler",
|
|
74
|
+
"create_put_record_handler",
|
|
75
|
+
"create_schema_handler",
|
|
76
|
+
"create_welcome_handler",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
logger = logging.getLogger(__name__)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def create_welcome_handler() -> Callable[[], Awaitable[ModelT]]:
|
|
83
|
+
"""Create an endpoint handler that returns an application welcome message.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
An async function that returns a welcome message.
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
interface = create_model("Welcome", message=(str, "Welcome to Auto-Rest!"))
|
|
90
|
+
|
|
91
|
+
async def welcome_handler() -> interface:
|
|
92
|
+
"""Return an application welcome message."""
|
|
93
|
+
|
|
94
|
+
return interface()
|
|
95
|
+
|
|
96
|
+
return welcome_handler
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def create_about_handler(name: str, version: str) -> Callable[[], Awaitable[ModelT]]:
|
|
100
|
+
"""Create an endpoint handler that returns the application name and version number.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
name: The application name.
|
|
104
|
+
version: The returned version identifier.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
An async function that returns aplication info.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
interface = create_model("Version", version=(str, version), name=(str, name))
|
|
111
|
+
|
|
112
|
+
async def about_handler() -> interface:
|
|
113
|
+
"""Return the application name and version number."""
|
|
114
|
+
|
|
115
|
+
return interface()
|
|
116
|
+
|
|
117
|
+
return about_handler
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def create_engine_handler(engine: DBEngine) -> Callable[[], Awaitable[ModelT]]:
|
|
121
|
+
"""Create an endpoint handler that returns configuration details for a database engine.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
engine: Database engine to return the configuration for.
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
An async function that returns database metadata.
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
interface = create_model("Meta",
|
|
131
|
+
dialect=(str, engine.dialect.name),
|
|
132
|
+
driver=(str, engine.dialect.driver),
|
|
133
|
+
database=(str, engine.url.database),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
async def meta_handler() -> interface:
|
|
137
|
+
"""Return metadata concerning the underlying application database."""
|
|
138
|
+
|
|
139
|
+
return interface()
|
|
140
|
+
|
|
141
|
+
return meta_handler
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def create_schema_handler(metadata: MetaData) -> Callable[[], Awaitable[ModelT]]:
|
|
145
|
+
"""Create an endpoint handler that returns the database schema.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
metadata: Metadata object containing the database schema.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
An async function that returns the database schema.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
# Define Pydantic models for column, table, and schema level data
|
|
155
|
+
column_interface = create_model("Column",
|
|
156
|
+
type=(str, ...),
|
|
157
|
+
nullable=(bool, ...),
|
|
158
|
+
default=(str | None, None),
|
|
159
|
+
primary_key=(bool, ...),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
table_interface = create_model("Table", columns=(dict[str, column_interface], ...))
|
|
163
|
+
schema_interface = create_model("Schema", tables=(dict[str, table_interface], ...))
|
|
164
|
+
|
|
165
|
+
async def schema_handler() -> schema_interface:
|
|
166
|
+
"""Return metadata concerning the underlying application database."""
|
|
167
|
+
|
|
168
|
+
return schema_interface(
|
|
169
|
+
tables={table_name: table_interface(columns={
|
|
170
|
+
column.name: column_interface(
|
|
171
|
+
type=str(column.type),
|
|
172
|
+
nullable=column.nullable,
|
|
173
|
+
default=str(column.default.arg) if column.default else None,
|
|
174
|
+
primary_key=column.primary_key
|
|
175
|
+
)
|
|
176
|
+
for column in table.columns
|
|
177
|
+
}) for table_name, table in metadata.tables.items()}
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
return schema_handler
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def create_list_records_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[list[ModelT]]]:
|
|
184
|
+
"""Create an endpoint handler that returns a list of records from a database table.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
engine: Database engine to use when executing queries.
|
|
188
|
+
model: The ORM object to use for database manipulations.
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
An async function that returns a list of records from the given database model.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
interface = create_db_interface(model)
|
|
195
|
+
|
|
196
|
+
async def list_records_handler(
|
|
197
|
+
response: Response,
|
|
198
|
+
session: DBSession = Depends(create_session_iterator(engine)),
|
|
199
|
+
pagination_params: dict[str, int] = Depends(get_pagination_params),
|
|
200
|
+
ordering_params: dict[str, int] = Depends(get_ordering_params),
|
|
201
|
+
) -> list[interface]:
|
|
202
|
+
"""Fetch a list of records from the database.
|
|
203
|
+
|
|
204
|
+
URL query parameters are used to enable filtering, ordering, and paginating returned values.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
query = select(model)
|
|
208
|
+
query = apply_pagination_params(query, pagination_params, response)
|
|
209
|
+
query = apply_ordering_params(query, ordering_params, response)
|
|
210
|
+
result = await execute_session_query(session, query)
|
|
211
|
+
return [interface.model_validate(record.__dict__) for record in result.scalars().all()]
|
|
212
|
+
|
|
213
|
+
return list_records_handler
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def create_get_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
|
|
217
|
+
"""Create a function for handling GET requests against a single record in the database.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
engine: Database engine to use when executing queries.
|
|
221
|
+
model: The ORM object to use for database manipulations.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
An async function that returns a single record from the given database model.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
interface = create_db_interface(model)
|
|
228
|
+
|
|
229
|
+
async def get_record_handler(
|
|
230
|
+
request: Request,
|
|
231
|
+
session: DBSession = Depends(create_session_iterator(engine)),
|
|
232
|
+
) -> interface:
|
|
233
|
+
"""Fetch a single record from the database."""
|
|
234
|
+
|
|
235
|
+
query = select(model).filter_by(**request.path_params)
|
|
236
|
+
result = await execute_session_query(session, query)
|
|
237
|
+
record = get_record_or_404(result)
|
|
238
|
+
return interface.model_validate(record.__dict__)
|
|
239
|
+
|
|
240
|
+
return get_record_handler
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def create_post_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
|
|
244
|
+
"""Create a function for handling POST requests against a record in the database.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
engine: Database engine to use when executing queries.
|
|
248
|
+
model: The ORM object to use for database manipulations.
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
An async function that handles record creation.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
interface = create_db_interface(model)
|
|
255
|
+
|
|
256
|
+
async def post_record_handler(
|
|
257
|
+
data: interface,
|
|
258
|
+
session: DBSession = Depends(create_session_iterator(engine)),
|
|
259
|
+
) -> interface:
|
|
260
|
+
"""Create a new record in the database."""
|
|
261
|
+
|
|
262
|
+
query = insert(model).values(**data.dict())
|
|
263
|
+
result = await execute_session_query(session, query)
|
|
264
|
+
record = get_record_or_404(result)
|
|
265
|
+
|
|
266
|
+
await commit_session(session)
|
|
267
|
+
return interface.model_validate(record.__dict__)
|
|
268
|
+
|
|
269
|
+
return post_record_handler
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def create_put_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
|
|
273
|
+
"""Create a function for handling PUT requests against a record in the database.
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
engine: Database engine to use when executing queries.
|
|
277
|
+
model: The ORM object to use for database manipulations.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
An async function that handles record updates.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
interface = create_db_interface(model)
|
|
284
|
+
|
|
285
|
+
async def put_record_handler(
|
|
286
|
+
request: Request,
|
|
287
|
+
data: interface,
|
|
288
|
+
session: DBSession = Depends(create_session_iterator(engine)),
|
|
289
|
+
) -> interface:
|
|
290
|
+
"""Replace record values in the database with the provided data."""
|
|
291
|
+
|
|
292
|
+
query = select(model).filter_by(**request.path_params)
|
|
293
|
+
result = await execute_session_query(session, query)
|
|
294
|
+
record = get_record_or_404(result)
|
|
295
|
+
|
|
296
|
+
for key, value in data.dict().items():
|
|
297
|
+
setattr(record, key, value)
|
|
298
|
+
|
|
299
|
+
await commit_session(session)
|
|
300
|
+
return interface.model_validate(record.__dict__)
|
|
301
|
+
|
|
302
|
+
return put_record_handler
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def create_patch_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
|
|
306
|
+
"""Create a function for handling PATCH requests against a record in the database.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
engine: Database engine to use when executing queries.
|
|
310
|
+
model: The ORM object to use for database manipulations.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
An async function that handles record updates.
|
|
314
|
+
"""
|
|
315
|
+
|
|
316
|
+
interface = create_db_interface(model)
|
|
317
|
+
|
|
318
|
+
async def patch_record_handler(
|
|
319
|
+
request: Request,
|
|
320
|
+
data: interface,
|
|
321
|
+
session: DBSession = Depends(create_session_iterator(engine)),
|
|
322
|
+
) -> interface:
|
|
323
|
+
"""Update record values in the database with the provided data."""
|
|
324
|
+
|
|
325
|
+
query = select(model).filter_by(**request.path_params)
|
|
326
|
+
result = await execute_session_query(session, query)
|
|
327
|
+
record = get_record_or_404(result)
|
|
328
|
+
|
|
329
|
+
for key, value in data.dict(exclude_unset=True).items():
|
|
330
|
+
setattr(record, key, value)
|
|
331
|
+
|
|
332
|
+
await commit_session(session)
|
|
333
|
+
return interface(record.__dict__)
|
|
334
|
+
|
|
335
|
+
return patch_record_handler
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def create_delete_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[None]]:
|
|
339
|
+
"""Create a function for handling DELETE requests against a record in the database.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
engine: Database engine to use when executing queries.
|
|
343
|
+
model: The ORM object to use for database manipulations.
|
|
344
|
+
|
|
345
|
+
Returns:
|
|
346
|
+
An async function that handles record deletion.
|
|
347
|
+
"""
|
|
348
|
+
|
|
349
|
+
async def delete_record_handler(
|
|
350
|
+
request: Request,
|
|
351
|
+
session: DBSession = Depends(create_session_iterator(engine)),
|
|
352
|
+
) -> None:
|
|
353
|
+
"""Delete a record from the database."""
|
|
354
|
+
|
|
355
|
+
query = select(model).filter_by(**request.path_params)
|
|
356
|
+
result = await execute_session_query(session, query)
|
|
357
|
+
record = get_record_or_404(result)
|
|
358
|
+
|
|
359
|
+
await delete_session_record(session, record)
|
|
360
|
+
await commit_session(session)
|
|
361
|
+
|
|
362
|
+
return delete_record_handler
|