auto-rest-api 0.1.0__tar.gz → 0.1.1__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.

Potentially problematic release.


This version of auto-rest-api might be problematic. Click here for more details.

@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: auto-rest-api
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Automatically map database schemas and deploy per-table REST API endpoints.
5
5
  License: GPL-3.0-only
6
6
  Keywords: Better,HPC,automatic,rest,api
7
7
  Author: Better HPC LLC
8
- Requires-Python: >=3.10
8
+ Requires-Python: >=3.11
9
9
  Classifier: Environment :: Web Environment
10
10
  Classifier: Intended Audience :: Developers
11
11
  Classifier: Intended Audience :: Information Technology
@@ -3,10 +3,9 @@
3
3
  import logging
4
4
  from pathlib import Path
5
5
 
6
- import uvicorn
7
6
  import yaml
8
- from fastapi import FastAPI
9
7
 
8
+ from .app import *
10
9
  from .cli import *
11
10
  from .models import *
12
11
  from .routers import *
@@ -80,7 +79,7 @@ def run_application(
80
79
 
81
80
  # Build an empty application and dynamically add the requested functionality.
82
81
  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)
82
+ app = create_app(app_title, app_version, enable_docs)
84
83
  app.include_router(create_welcome_router(), prefix="")
85
84
  app.include_router(create_meta_router(db_conn, db_meta, app_title, app_version), prefix="/meta")
86
85
 
@@ -90,4 +89,4 @@ def run_application(
90
89
 
91
90
  # Launch the API server.
92
91
  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")
92
+ run_server(app, server_host, server_port)
@@ -0,0 +1,67 @@
1
+ """
2
+ The `app` module provides factory functions and utilities for building and
3
+ deploying Fast-API applications.
4
+
5
+
6
+ !!! example "Example: Build and Deploy an API"
7
+
8
+ ```python
9
+ from auto_rest.app import create_app, run_server
10
+
11
+ app = create_app(app_title="My Application", app_version="1.2.3", enable_docs=True)
12
+ ... # Add endpoints to the application here
13
+ run_server(app, host="127.0.0.1", port=8081)
14
+ ```
15
+ """
16
+
17
+ import uvicorn
18
+ from fastapi import FastAPI
19
+ from fastapi.middleware.cors import CORSMiddleware
20
+
21
+ __all__ = ["create_app", "run_server"]
22
+
23
+
24
+ def create_app(app_title: str, app_version: str, enable_docs: bool) -> FastAPI:
25
+ """Create and configure a FastAPI application instance.
26
+
27
+ This function initializes a FastAPI app with a customizable title, version,
28
+ and optional documentation routes. It also configures application middleware
29
+ for CORS policies.
30
+
31
+ Args:
32
+ app_title: The title of the FastAPI application.
33
+ app_version: The version of the FastAPI application.
34
+ enable_docs: Whether to enable the `/docs/` endpoint.
35
+
36
+ Returns:
37
+ FastAPI: A configured FastAPI application instance.
38
+ """
39
+
40
+ app = FastAPI(
41
+ title=app_title,
42
+ version=app_version,
43
+ docs_url="/docs/" if enable_docs else None,
44
+ redoc_url=None,
45
+ )
46
+
47
+ app.add_middleware(
48
+ CORSMiddleware,
49
+ allow_origins=["*"],
50
+ allow_credentials=True,
51
+ allow_methods=["*"],
52
+ allow_headers=["*"],
53
+ )
54
+
55
+ return app
56
+
57
+
58
+ def run_server(app: FastAPI, host: str, port: int) -> None: # pragma: no cover
59
+ """Deploy a FastAPI application server.
60
+
61
+ Args:
62
+ app: The FastAPI application to run.
63
+ host: The hostname or IP address for the server to bind to.
64
+ port: The port number for the server to listen on.
65
+ """
66
+
67
+ uvicorn.run(app, host=host, port=port, log_level="error")
@@ -55,7 +55,7 @@ from typing import Awaitable, Callable
55
55
 
56
56
  from fastapi import Depends, Response
57
57
  from pydantic import create_model
58
- from pydantic.main import ModelT
58
+ from pydantic.main import BaseModel as PydanticModel
59
59
  from sqlalchemy import insert, MetaData, select
60
60
  from starlette.requests import Request
61
61
 
@@ -79,7 +79,7 @@ __all__ = [
79
79
  logger = logging.getLogger(__name__)
80
80
 
81
81
 
82
- def create_welcome_handler() -> Callable[[], Awaitable[ModelT]]:
82
+ def create_welcome_handler() -> Callable[[], Awaitable[PydanticModel]]:
83
83
  """Create an endpoint handler that returns an application welcome message.
84
84
 
85
85
  Returns:
@@ -96,7 +96,7 @@ def create_welcome_handler() -> Callable[[], Awaitable[ModelT]]:
96
96
  return welcome_handler
97
97
 
98
98
 
99
- def create_about_handler(name: str, version: str) -> Callable[[], Awaitable[ModelT]]:
99
+ def create_about_handler(name: str, version: str) -> Callable[[], Awaitable[PydanticModel]]:
100
100
  """Create an endpoint handler that returns the application name and version number.
101
101
 
102
102
  Args:
@@ -104,7 +104,7 @@ def create_about_handler(name: str, version: str) -> Callable[[], Awaitable[Mode
104
104
  version: The returned version identifier.
105
105
 
106
106
  Returns:
107
- An async function that returns aplication info.
107
+ An async function that returns application info.
108
108
  """
109
109
 
110
110
  interface = create_model("Version", version=(str, version), name=(str, name))
@@ -117,7 +117,7 @@ def create_about_handler(name: str, version: str) -> Callable[[], Awaitable[Mode
117
117
  return about_handler
118
118
 
119
119
 
120
- def create_engine_handler(engine: DBEngine) -> Callable[[], Awaitable[ModelT]]:
120
+ def create_engine_handler(engine: DBEngine) -> Callable[[], Awaitable[PydanticModel]]:
121
121
  """Create an endpoint handler that returns configuration details for a database engine.
122
122
 
123
123
  Args:
@@ -141,7 +141,7 @@ def create_engine_handler(engine: DBEngine) -> Callable[[], Awaitable[ModelT]]:
141
141
  return meta_handler
142
142
 
143
143
 
144
- def create_schema_handler(metadata: MetaData) -> Callable[[], Awaitable[ModelT]]:
144
+ def create_schema_handler(metadata: MetaData) -> Callable[[], Awaitable[PydanticModel]]:
145
145
  """Create an endpoint handler that returns the database schema.
146
146
 
147
147
  Args:
@@ -180,7 +180,7 @@ def create_schema_handler(metadata: MetaData) -> Callable[[], Awaitable[ModelT]]
180
180
  return schema_handler
181
181
 
182
182
 
183
- def create_list_records_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[list[ModelT]]]:
183
+ def create_list_records_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[list[PydanticModel]]]:
184
184
  """Create an endpoint handler that returns a list of records from a database table.
185
185
 
186
186
  Args:
@@ -196,8 +196,8 @@ def create_list_records_handler(engine: DBEngine, model: DBModel) -> Callable[..
196
196
  async def list_records_handler(
197
197
  response: Response,
198
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),
199
+ pagination_params: dict[str, int] = create_pagination_dependency(model),
200
+ ordering_params: dict[str, int] = create_ordering_dependency(model),
201
201
  ) -> list[interface]:
202
202
  """Fetch a list of records from the database.
203
203
 
@@ -213,7 +213,7 @@ def create_list_records_handler(engine: DBEngine, model: DBModel) -> Callable[..
213
213
  return list_records_handler
214
214
 
215
215
 
216
- def create_get_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
216
+ def create_get_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[PydanticModel]]:
217
217
  """Create a function for handling GET requests against a single record in the database.
218
218
 
219
219
  Args:
@@ -240,7 +240,7 @@ def create_get_record_handler(engine: DBEngine, model: DBModel) -> Callable[...,
240
240
  return get_record_handler
241
241
 
242
242
 
243
- def create_post_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
243
+ def create_post_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[PydanticModel]]:
244
244
  """Create a function for handling POST requests against a record in the database.
245
245
 
246
246
  Args:
@@ -269,7 +269,7 @@ def create_post_record_handler(engine: DBEngine, model: DBModel) -> Callable[...
269
269
  return post_record_handler
270
270
 
271
271
 
272
- def create_put_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
272
+ def create_put_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[PydanticModel]]:
273
273
  """Create a function for handling PUT requests against a record in the database.
274
274
 
275
275
  Args:
@@ -302,7 +302,7 @@ def create_put_record_handler(engine: DBEngine, model: DBModel) -> Callable[...,
302
302
  return put_record_handler
303
303
 
304
304
 
305
- def create_patch_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[ModelT]]:
305
+ def create_patch_record_handler(engine: DBEngine, model: DBModel) -> Callable[..., Awaitable[PydanticModel]]:
306
306
  """Create a function for handling PATCH requests against a record in the database.
307
307
 
308
308
  Args:
@@ -36,7 +36,7 @@ import logging
36
36
  from pathlib import Path
37
37
  from typing import Callable
38
38
 
39
- from pydantic.main import create_model, ModelT
39
+ from pydantic.main import create_model, BaseModel as PydanticModel
40
40
  from sqlalchemy import create_engine, Engine, MetaData, URL
41
41
  from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine
42
42
  from sqlalchemy.orm import declarative_base, Session
@@ -55,8 +55,10 @@ __all__ = [
55
55
 
56
56
  logger = logging.getLogger(__name__)
57
57
 
58
+ Base = declarative_base()
59
+
58
60
  # Base classes and typing objects.
59
- DBModel = declarative_base()
61
+ DBModel = type[Base]
60
62
  DBEngine = Engine | AsyncEngine
61
63
  DBSession = Session | AsyncSession
62
64
 
@@ -174,7 +176,7 @@ def create_db_models(metadata: MetaData) -> dict[str, DBModel]:
174
176
  logger.debug(f"> Creating model for table {table_name}.")
175
177
  models[table_name] = type(
176
178
  table_name.capitalize(),
177
- (DBModel,),
179
+ (Base,),
178
180
  {"__table__": table},
179
181
  )
180
182
 
@@ -182,7 +184,7 @@ def create_db_models(metadata: MetaData) -> dict[str, DBModel]:
182
184
  return models
183
185
 
184
186
 
185
- def create_db_interface(model: DBModel) -> type[ModelT]:
187
+ def create_db_interface(model: DBModel) -> type[PydanticModel]:
186
188
  """Create a Pydantic interface for a SQLAlchemy model.
187
189
 
188
190
  Args:
@@ -0,0 +1,175 @@
1
+ """
2
+ The `params` module provides utilities for extracting and applying query
3
+ parameters from incoming HTTP requests. These utilities ensure the consistent
4
+ parsing, validation, and application of query parameters, and automatically
5
+ update HTTP response headers to reflect applied query options.
6
+
7
+ Parameter functions are designed in pairs of two. The first function is a
8
+ factory for creating an injectable FastAPI dependency. The dependency
9
+ is used to parse parameters from incoming requests and applies high level
10
+ validation against the parsed values. The second function to applies the
11
+ validated arguments onto a SQLAlchemy query and returns the updated query.
12
+
13
+ !!! example "Example: Parameter Parsing and Application"
14
+
15
+ ```python
16
+ from fastapi import FastAPI, Response
17
+ from sqlalchemy import select
18
+ from auto_rest.query_params import create_pagination_dependency, apply_pagination_params
19
+
20
+ app = FastAPI()
21
+
22
+ @app.get("/items/")
23
+ async def list_items(
24
+ pagination_params: dict = create_pagination_dependency(model),
25
+ response: Response
26
+ ):
27
+ query = select(model)
28
+ query = apply_pagination_params(query, pagination_params, response)
29
+ return ... # Logic to further process and execute the query goes here
30
+ ```
31
+ """
32
+ from collections.abc import Callable
33
+ from typing import Literal
34
+
35
+ from fastapi import Depends, Query
36
+ from sqlalchemy import asc, desc
37
+ from sqlalchemy.sql.selectable import Select
38
+ from starlette.responses import Response
39
+
40
+ from .models import DBModel
41
+
42
+ __all__ = [
43
+ "apply_ordering_params",
44
+ "apply_pagination_params",
45
+ "create_ordering_dependency",
46
+ "create_pagination_dependency",
47
+ ]
48
+
49
+
50
+ def create_ordering_dependency(model: type[DBModel]) -> Callable[..., dict]:
51
+ """Create an injectable dependency for fetching ordering arguments from query parameters.
52
+
53
+ Args:
54
+ model: The database model to create the dependency for.
55
+
56
+ Returns:
57
+ An injectable FastAPI dependency.
58
+ """
59
+
60
+ columns = tuple(model.__table__.columns.keys())
61
+
62
+ def get_ordering_params(
63
+ _order_by_: Literal[*columns] = Query(None, description="The field name to sort by."),
64
+ _direction_: Literal["asc", "desc"] = Query("asc", description="Sort results in 'asc' or 'desc' order.")
65
+ ) -> dict:
66
+ """Extract ordering parameters from request query parameters.
67
+
68
+ Args:
69
+ _order_by_: The field to order by.
70
+ _direction_: The direction to order by.
71
+
72
+ Returns:
73
+ dict: A dictionary containing the `order_by` and `direction` values.
74
+ """
75
+
76
+ return {"order_by": _order_by_, "direction": _direction_}
77
+
78
+ return Depends(get_ordering_params)
79
+
80
+
81
+ def apply_ordering_params(query: Select, params: dict, response: Response) -> Select:
82
+ """Apply ordering to a database query.
83
+
84
+ Returns a copy of the provided query with ordering parameters applied.
85
+ This method is compatible with parameters returned by the `get_ordering_params` method.
86
+ Ordering is not applied for invalid params, but response headers are still set.
87
+
88
+ Args:
89
+ query: The database query to apply parameters to.
90
+ params: A dictionary containing parsed URL parameters.
91
+ response: The outgoing HTTP response object.
92
+
93
+ Returns:
94
+ A copy of the query modified to return ordered values.
95
+ """
96
+
97
+ order_by = params.get("order_by")
98
+ direction = params.get("direction")
99
+
100
+ # Set common response headers
101
+ response.headers["X-Order-By"] = str(order_by)
102
+ response.headers["X-Order-Direction"] = str(direction)
103
+
104
+ if order_by is None:
105
+ response.headers["X-Order-Applied"] = "false"
106
+ return query
107
+
108
+ # Default to ascending order for an invalid ordering direction
109
+ response.headers["X-Order-Applied"] = "true"
110
+ if direction == "desc":
111
+ return query.order_by(desc(order_by))
112
+
113
+ else:
114
+ return query.order_by(asc(order_by))
115
+
116
+
117
+ def create_pagination_dependency(model: type[DBModel]) -> Callable[..., dict]:
118
+ """Create an injectable dependency for fetching pagination arguments from query parameters.
119
+
120
+ Args:
121
+ model: The database model to create the dependency for.
122
+
123
+ Returns:
124
+ An injectable FastAPI dependency.
125
+ """
126
+
127
+ def get_pagination_params(
128
+ _limit_: int = Query(0, ge=0, description="The maximum number of records to return."),
129
+ _offset_: int = Query(0, ge=0, description="The starting index of the returned records."),
130
+ ) -> dict[str, int]:
131
+ """Extract pagination parameters from request query parameters.
132
+
133
+ Args:
134
+ _limit_: The maximum number of records to return.
135
+ _offset_: The starting index of the returned records.
136
+
137
+ Returns:
138
+ dict: A dictionary containing the `limit` and `offset` values.
139
+ """
140
+
141
+ return {"limit": _limit_, "offset": _offset_}
142
+
143
+ return Depends(get_pagination_params)
144
+
145
+
146
+ def apply_pagination_params(query: Select, params: dict[str, int], response: Response) -> Select:
147
+ """Apply pagination to a database query.
148
+
149
+ Returns a copy of the provided query with offset and limit parameters applied.
150
+ This method is compatible with parameters returned by the `get_pagination_params` method.
151
+ Pagination is not applied for invalid params, but response headers are still set.
152
+
153
+ Args:
154
+ query: The database query to apply parameters to.
155
+ params: A dictionary containing parsed URL parameters.
156
+ response: The outgoing HTTP response object.
157
+
158
+ Returns:
159
+ A copy of the query modified to only return the paginated values.
160
+ """
161
+
162
+ limit = params.get("limit")
163
+ offset = params.get("offset")
164
+
165
+ # Set common response headers
166
+ response.headers["X-Pagination-Limit"] = str(limit)
167
+ response.headers["X-Pagination-Offset"] = str(offset)
168
+
169
+ # Do not apply pagination if not requested
170
+ if limit in (0, None):
171
+ response.headers["X-Pagination-Applied"] = "false"
172
+ return query
173
+
174
+ response.headers["X-Pagination-Applied"] = "true"
175
+ return query.offset(offset or 0).limit(limit)
@@ -58,7 +58,7 @@ def create_meta_router(engine: DBEngine, metadata: MetaData, name: str, version:
58
58
  Args:
59
59
  engine: The database engine used to facilitate database interactions.
60
60
  metadata: The metadata object containing the database schema.
61
- version: The application name.
61
+ name: The application name.
62
62
  version: The application versionnumber.
63
63
 
64
64
  Returns:
@@ -89,15 +89,11 @@ def create_model_router(engine: DBEngine, model: DBModel, writeable: bool = Fals
89
89
  router = APIRouter()
90
90
 
91
91
  # Construct path parameters from primary key columns
92
- pk_columns = model.__table__.primary_key.columns
93
- path_params_url = "/".join(f"{{{col.name}}}" for col in pk_columns)
92
+ pk_columns = sorted(column.name for column in model.__table__.primary_key.columns)
93
+ path_params_url = "/".join(f"{{{col_name}}}" for col_name in pk_columns)
94
94
  path_params_openapi = {
95
95
  "parameters": [
96
- {
97
- "name": col.name,
98
- "in": "path",
99
- "required": True
100
- } for col in pk_columns
96
+ {"name": col_name, "in": "path", "required": True} for col_name in pk_columns
101
97
  ]
102
98
  }
103
99
 
@@ -113,7 +109,6 @@ def create_model_router(engine: DBEngine, model: DBModel, writeable: bool = Fals
113
109
  endpoint=create_list_records_handler(engine, model),
114
110
  status_code=status.HTTP_200_OK,
115
111
  tags=[model.__name__],
116
- openapi_extra=path_params_openapi
117
112
  )
118
113
 
119
114
  router.add_api_route(
@@ -135,7 +130,6 @@ def create_model_router(engine: DBEngine, model: DBModel, writeable: bool = Fals
135
130
  endpoint=create_post_record_handler(engine, model),
136
131
  status_code=status.HTTP_201_CREATED,
137
132
  tags=[model.__name__],
138
- openapi_extra=path_params_openapi
139
133
  )
140
134
 
141
135
  router.add_api_route(
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
4
4
 
5
5
  [project]
6
6
  name = "auto-rest-api"
7
- version = "0.1.0"
7
+ version = "0.1.1"
8
8
  readme = "README.md"
9
9
  description = "Automatically map database schemas and deploy per-table REST API endpoints."
10
10
  authors = [{ name = "Better HPC LLC" }]
@@ -24,7 +24,7 @@ classifiers = [
24
24
  "Topic :: Software Development",
25
25
  "Typing :: Typed"
26
26
  ]
27
- requires-python = ">=3.10"
27
+ requires-python = ">=3.11"
28
28
  dependencies = [
29
29
  "aiomysql~=0.2",
30
30
  "aioodbc~=0.5",
@@ -1,147 +0,0 @@
1
- """
2
- The `params` module provides utilities for extracting and applying query
3
- parameters from incoming HTTP requests. These utilities ensure the consistent
4
- parsing, validation, and application of query parameters, and automatically
5
- update HTTP response headers to reflect applied query options.
6
-
7
- Parameter functions are designed in pairs. A *get* function is used to parse
8
- parameters from a FastAPI request and an *apply* function is used to apply
9
- the arguments onto a SQLAlchemy query.
10
-
11
- !!! example "Example: Parameter Parsing and Application"
12
-
13
- When handling URL parameters, a *get* function is injected as a dependency
14
- into the signature of the request handler. The parsed parameter dictionary
15
- is then passed to an *apply* function.
16
-
17
- ```python
18
- from fastapi import FastAPI, Response, Depends
19
- from sqlalchemy import select
20
- from auto_rest.query_params import get_pagination_params, apply_pagination_params
21
-
22
- app = FastAPI()
23
-
24
- @app.get("/items/")
25
- async def list_items(
26
- pagination_params: dict = Depends(get_pagination_params),
27
- response: Response
28
- ):
29
- query = select(SomeModel)
30
- query = apply_pagination_params(query, pagination_params, response)
31
- return ... # Logic to further process and execute the query goes here
32
- ```
33
- """
34
-
35
- from typing import Literal
36
-
37
- from fastapi import Query
38
- from sqlalchemy import asc, desc
39
- from sqlalchemy.sql.selectable import Select
40
- from starlette.responses import Response
41
-
42
- __all__ = [
43
- "apply_ordering_params",
44
- "apply_pagination_params",
45
- "get_ordering_params",
46
- "get_pagination_params",
47
- ]
48
-
49
-
50
- def get_pagination_params(
51
- _limit_: int = Query(10, ge=0, description="The maximum number of records to return."),
52
- _offset_: int = Query(0, ge=0, description="The starting index of the returned records."),
53
- ) -> dict[str, int]:
54
- """Extract pagination parameters from request query parameters.
55
-
56
- Args:
57
- _limit_: The maximum number of records to return.
58
- _offset_: The starting index of the returned records.
59
-
60
- Returns:
61
- dict: A dictionary containing the `limit` and `offset` values.
62
- """
63
-
64
- return {"limit": _limit_, "offset": _offset_}
65
-
66
-
67
- def apply_pagination_params(query: Select, params: dict[str, int], response: Response) -> Select:
68
- """Apply pagination to a database query.
69
-
70
- Returns a copy of the provided query with offset and limit parameters applied.
71
- Compatible with parameters returned by the `get_pagination_params` method.
72
-
73
- Args:
74
- query: The database query to apply parameters to.
75
- params: A dictionary containing parsed URL parameters.
76
- response: The outgoing HTTP response object.
77
-
78
- Returns:
79
- A copy of the query modified to only return the paginated values.
80
- """
81
-
82
- limit = params.get("limit", 0)
83
- offset = params.get("offset", 0)
84
-
85
- if limit < 0 or offset < 0:
86
- raise ValueError("Pagination parameters must be greater than or equal to zero.")
87
-
88
- if limit == 0:
89
- response.headers["X-Pagination-Applied"] = "false"
90
- return query
91
-
92
- response.headers["X-Pagination-Applied"] = "true"
93
- response.headers["X-Pagination-Limit"] = str(limit)
94
- response.headers["X-Pagination-Offset"] = str(offset)
95
- return query.offset(offset).limit(limit)
96
-
97
-
98
- def get_ordering_params(
99
- _order_by_: str | None = Query(None, description="The field name to sort by."),
100
- _direction_: Literal["asc", "desc"] = Query("asc", description="Sort results in 'asc' or 'desc' order.")
101
- ) -> dict:
102
- """Extract ordering parameters from request query parameters.
103
-
104
- Args:
105
- _order_by_: The field to order by.
106
- _direction_: The direction to order by.
107
-
108
- Returns:
109
- dict: A dictionary containing the `order_by` and `direction` values.
110
- """
111
-
112
- return {"order_by": _order_by_, "direction": _direction_}
113
-
114
-
115
- def apply_ordering_params(query: Select, params: dict, response: Response) -> Select:
116
- """Apply ordering to a database query.
117
-
118
- Returns a copy of the provided query with ordering parameters applied.
119
- Compatible with parameters returned by the `get_ordering_params` method.
120
-
121
- Args:
122
- query: The database query to apply parameters to.
123
- params: A dictionary containing parsed URL parameters.
124
- response: The outgoing HTTP response object.
125
-
126
- Returns:
127
- A copy of the query modified to return ordered values.
128
- """
129
-
130
- order_by = params.get("order_by")
131
- direction = params.get("direction", "asc")
132
-
133
- if not order_by:
134
- response.headers["X-Order-Applied"] = "false"
135
- return query
136
-
137
- response.headers["X-Order-Applied"] = "true"
138
- response.headers["X-Order-By"] = order_by
139
- response.headers["X-Order-Direction"] = direction
140
-
141
- if direction == "asc":
142
- return query.order_by(asc(order_by))
143
-
144
- if direction == "desc":
145
- return query.order_by(desc(order_by))
146
-
147
- raise ValueError("Ordering direction must be 'asc' or 'desc'.")
File without changes
File without changes