contentgrid-extension-helpers 0.0.3__tar.gz → 0.0.4__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.
- {contentgrid_extension_helpers-0.0.3/src/contentgrid_extension_helpers.egg-info → contentgrid_extension_helpers-0.0.4}/PKG-INFO +1 -1
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/responses/hal.py +13 -7
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4/src/contentgrid_extension_helpers.egg-info}/PKG-INFO +1 -1
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers.egg-info/SOURCES.txt +3 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/fixtures.py +12 -0
- contentgrid_extension_helpers-0.0.4/tests/server/routers/bar_router.py +42 -0
- contentgrid_extension_helpers-0.0.4/tests/server/server_with_multi_routers.py +16 -0
- contentgrid_extension_helpers-0.0.4/tests/server/types/bar.py +23 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_hal_responses.py +258 -2
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/LICENSE +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/README.md +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/pyproject.toml +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/pytest.ini +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/requirements.txt +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/setup.cfg +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/setup.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/authentication/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/authentication/oidc.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/authentication/user.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/config.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/authentication/user.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/clients/contentgrid/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/clients/contentgrid/client_factory.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/clients/contentgrid/extension_flow_factory.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/clients/contentgrid/service_account_factory.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/db/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/db/base_factory.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/db/postgres.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/db/sqlite.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/repositories/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/dependencies/sqlalch/repositories/base_repository.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/exceptions.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/logging/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/logging/json_logging.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/middleware/exception_middleware.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/problem_response.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/responses/__init__.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers/structured_output/model_deny.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers.egg-info/dependency_links.txt +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers.egg-info/requires.txt +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/src/contentgrid_extension_helpers.egg-info/top_level.txt +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/base_server.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/dependencies.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/repositories/foo_repo.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/routers/client_router.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/routers/foo_router.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/server_with_db.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/types/foo.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/server/types/hal_object.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_database_application.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_exception_middleware.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_extension_flow_dependency.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_foo_router.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_logging.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_sqlalch_db.py +0 -0
- {contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_user_dependency.py +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
from enum import Enum
|
|
1
2
|
from urllib.parse import urlencode
|
|
2
3
|
from fastapi import FastAPI
|
|
3
4
|
from fastapi.routing import APIRoute
|
|
@@ -9,13 +10,17 @@ from contentgrid_hal_client.hal import HALShape, HALLink
|
|
|
9
10
|
from contentgrid_hal_client.hal_forms import HALFormsTemplate, HALFormsMethod, HALFormsPropertyType, HALFormsProperty
|
|
10
11
|
import uri_template
|
|
11
12
|
|
|
12
|
-
def get_route_from_app(app: FastAPI, endpoint_function: str) -> APIRoute:
|
|
13
|
+
def get_route_from_app(app: FastAPI, endpoint_function: str, tags : Optional[List[str | Enum]] = None) -> APIRoute:
|
|
13
14
|
for route in app.routes:
|
|
14
15
|
if isinstance(route, APIRoute) and route.name == endpoint_function:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if tags is None:
|
|
17
|
+
return route
|
|
18
|
+
else:
|
|
19
|
+
for tag in route.tags:
|
|
20
|
+
if tag in tags:
|
|
21
|
+
return route
|
|
22
|
+
error_message = f"No route found for endpoint {endpoint_function}"
|
|
23
|
+
raise ValueError(error_message)
|
|
19
24
|
|
|
20
25
|
def _add_params(url : str, params: Optional[Dict[str, str]] = None) -> str:
|
|
21
26
|
if params:
|
|
@@ -66,6 +71,7 @@ def extract_hal_forms_properties_from_pydantic_base_model(
|
|
|
66
71
|
|
|
67
72
|
class LinkForType(BaseModel):
|
|
68
73
|
endpoint_function_name: str
|
|
74
|
+
tags: Optional[List[str | Enum]] = None
|
|
69
75
|
templated: bool = False
|
|
70
76
|
path_params: Union[dict[str, str], Callable[["FastAPIHALResponse"], dict[str, str]]] = Field(default_factory=dict)
|
|
71
77
|
params: Union[dict[str, Union[str, int, float]], Callable[["FastAPIHALResponse"], dict[str, Union[str, int, float]]]] = Field(default_factory=dict)
|
|
@@ -111,7 +117,7 @@ class FastAPIHALResponse(HALShape):
|
|
|
111
117
|
return None
|
|
112
118
|
|
|
113
119
|
if hasattr(self.__class__, '_app') and self.__class__._app:
|
|
114
|
-
route = get_route_from_app(self.__class__._app, link.endpoint_function_name)
|
|
120
|
+
route = get_route_from_app(self.__class__._app, link.endpoint_function_name, tags=link.tags)
|
|
115
121
|
|
|
116
122
|
if hasattr(self.__class__, '_server_url'):
|
|
117
123
|
uri = f"{self.__class__._server_url}{route.path}"
|
|
@@ -164,7 +170,7 @@ class FastAPIHALResponse(HALShape):
|
|
|
164
170
|
continue
|
|
165
171
|
|
|
166
172
|
uri = hallink.uri
|
|
167
|
-
route = get_route_from_app(self.__class__._app, template_value.endpoint_function_name)
|
|
173
|
+
route = get_route_from_app(self.__class__._app, template_value.endpoint_function_name, tags=template_value.tags)
|
|
168
174
|
body_model, default_data = get_body_from_route(route=route)
|
|
169
175
|
if body_model is None:
|
|
170
176
|
properties = []
|
|
@@ -46,8 +46,11 @@ tests/test_user_dependency.py
|
|
|
46
46
|
tests/server/base_server.py
|
|
47
47
|
tests/server/dependencies.py
|
|
48
48
|
tests/server/server_with_db.py
|
|
49
|
+
tests/server/server_with_multi_routers.py
|
|
49
50
|
tests/server/repositories/foo_repo.py
|
|
51
|
+
tests/server/routers/bar_router.py
|
|
50
52
|
tests/server/routers/client_router.py
|
|
51
53
|
tests/server/routers/foo_router.py
|
|
54
|
+
tests/server/types/bar.py
|
|
52
55
|
tests/server/types/foo.py
|
|
53
56
|
tests/server/types/hal_object.py
|
{contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/fixtures.py
RENAMED
|
@@ -11,12 +11,14 @@ os.environ["DB_TYPE"] = "postgresql+psycopg2"
|
|
|
11
11
|
from testcontainers.postgres import PostgresContainer
|
|
12
12
|
from server.base_server import app
|
|
13
13
|
from server.server_with_db import app_with_db
|
|
14
|
+
from server.server_with_multi_routers import app_with_multi_routers
|
|
14
15
|
|
|
15
16
|
postgres = PostgresContainer("postgres:16-alpine", dbname="contentgrid_test")
|
|
16
17
|
|
|
17
18
|
# Global variables to store the apps after container setup
|
|
18
19
|
_app_with_db = app_with_db
|
|
19
20
|
_simple_app = app
|
|
21
|
+
_app_with_multi_routers = app_with_multi_routers
|
|
20
22
|
|
|
21
23
|
@fixture(scope="session")
|
|
22
24
|
def postgres_session_factory():
|
|
@@ -68,11 +70,21 @@ def simple_app():
|
|
|
68
70
|
raise RuntimeError("Simple app not initialized.")
|
|
69
71
|
return _simple_app
|
|
70
72
|
|
|
73
|
+
@fixture
|
|
74
|
+
def app_with_multi_routers():
|
|
75
|
+
"""Provides the FastAPI app instance with multiple routers."""
|
|
76
|
+
return _app_with_multi_routers
|
|
77
|
+
|
|
71
78
|
@fixture
|
|
72
79
|
def client_no_db(simple_app):
|
|
73
80
|
"""Provides a TestClient for the app without database."""
|
|
74
81
|
return TestClient(simple_app)
|
|
75
82
|
|
|
83
|
+
@fixture
|
|
84
|
+
def client_multi_routers(app_with_multi_routers):
|
|
85
|
+
"""Provides a TestClient for the app with multiple routers."""
|
|
86
|
+
return TestClient(app_with_multi_routers)
|
|
87
|
+
|
|
76
88
|
@fixture
|
|
77
89
|
def client(app_with_db, clean_database):
|
|
78
90
|
"""Provides a TestClient for the app with database."""
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter
|
|
6
|
+
|
|
7
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALCollection
|
|
8
|
+
|
|
9
|
+
from server.types.bar import Bar, BarCreate
|
|
10
|
+
|
|
11
|
+
from contentgrid_extension_helpers.responses.hal import (
|
|
12
|
+
FastAPIHALResponse,
|
|
13
|
+
FastAPIHALCollection,
|
|
14
|
+
HALLinkFor,
|
|
15
|
+
HALTemplateFor,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
def create_bar_router(tags=[]):
|
|
19
|
+
bar_router = APIRouter(tags=tags)
|
|
20
|
+
bars = []
|
|
21
|
+
|
|
22
|
+
@bar_router.get('/bars', response_model=FastAPIHALCollection, response_model_exclude_none=True)
|
|
23
|
+
def get_bars():
|
|
24
|
+
return FastAPIHALCollection[Bar](
|
|
25
|
+
_links={
|
|
26
|
+
"self" : HALLinkFor(endpoint_function_name="get_bars", tags=tags)
|
|
27
|
+
},
|
|
28
|
+
_embedded={
|
|
29
|
+
"bars" : bars
|
|
30
|
+
},
|
|
31
|
+
_templates={
|
|
32
|
+
"create_bar" : HALTemplateFor(endpoint_function_name="create_bar", tags=tags)
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
@bar_router.post('/bars', response_model=Bar, response_model_exclude_none=True)
|
|
37
|
+
def create_bar(bar : BarCreate) -> Bar:
|
|
38
|
+
new_bar = Bar(**bar.model_dump(), tags=tags)
|
|
39
|
+
bars.append(new_bar)
|
|
40
|
+
return new_bar
|
|
41
|
+
|
|
42
|
+
return bar_router
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALResponse
|
|
3
|
+
from server.base_server import app
|
|
4
|
+
FastAPIHALResponse.init_app(app)
|
|
5
|
+
from server.routers.bar_router import create_bar_router
|
|
6
|
+
|
|
7
|
+
app_with_multi_routers = app
|
|
8
|
+
|
|
9
|
+
bar1_tags = ["bar1"]
|
|
10
|
+
bar2_tags = ["bar2", "a random tag"]
|
|
11
|
+
app_with_multi_routers.include_router(create_bar_router(tags=bar1_tags), prefix="/bar1", tags=bar1_tags)
|
|
12
|
+
app_with_multi_routers.include_router(create_bar_router(tags=bar2_tags), prefix='/bar2', tags=bar2_tags)
|
|
13
|
+
|
|
14
|
+
if __name__ == "__main__":
|
|
15
|
+
import uvicorn
|
|
16
|
+
uvicorn.run(app_with_multi_routers, host="0.0.0.0", port=5004, workers=1)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from typing import Optional, List
|
|
2
|
+
from pydantic import BaseModel, Field as PydanticField
|
|
3
|
+
from sqlmodel import Field, SQLModel, Relationship
|
|
4
|
+
|
|
5
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALResponse
|
|
6
|
+
|
|
7
|
+
from contentgrid_extension_helpers.responses.hal import HALLinkFor
|
|
8
|
+
|
|
9
|
+
class BarBase(BaseModel):
|
|
10
|
+
name: str = Field(description="Name of the Bar")
|
|
11
|
+
location : str = Field(default=None, description="Location of the bar")
|
|
12
|
+
|
|
13
|
+
class Bar(FastAPIHALResponse, BarBase):
|
|
14
|
+
|
|
15
|
+
def __init__(self, tags, **kwargs):
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self.links = {
|
|
18
|
+
"bars" : HALLinkFor(endpoint_function_name="get_bars", tags=tags)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class BarCreate(BarBase):
|
|
22
|
+
# Model for creating a new Bar
|
|
23
|
+
secret_drink_recipe : str = Field(..., description="A secret recipe for a drink")
|
|
@@ -18,7 +18,7 @@ from contentgrid_extension_helpers.responses.hal import (
|
|
|
18
18
|
_add_params
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
from fixtures import simple_app, app_with_db
|
|
21
|
+
from fixtures import simple_app, app_with_db, app_with_multi_routers, client_multi_routers
|
|
22
22
|
|
|
23
23
|
class User(BaseModel):
|
|
24
24
|
id: int
|
|
@@ -1442,5 +1442,261 @@ class TestHALJSONSchemaGeneration:
|
|
|
1442
1442
|
assert any("UserCollection" in name for name in schema_names)
|
|
1443
1443
|
|
|
1444
1444
|
|
|
1445
|
+
class TestTagsFiltering:
|
|
1446
|
+
"""Test tags filtering functionality for routing multiple endpoints with the same name"""
|
|
1447
|
+
|
|
1448
|
+
def test_get_route_from_app_with_tags(self, app_with_multi_routers):
|
|
1449
|
+
"""Test retrieving route with specific tags"""
|
|
1450
|
+
from contentgrid_extension_helpers.responses.hal import get_route_from_app
|
|
1451
|
+
|
|
1452
|
+
# Get route with tag filter
|
|
1453
|
+
route_bar1 = get_route_from_app(app_with_multi_routers, "get_bars", tags=["bar1"])
|
|
1454
|
+
assert route_bar1.path == "/bar1/bars"
|
|
1455
|
+
|
|
1456
|
+
route_bar2 = get_route_from_app(app_with_multi_routers, "get_bars", tags=["bar2"])
|
|
1457
|
+
assert route_bar2.path == "/bar2/bars"
|
|
1458
|
+
|
|
1459
|
+
def test_get_route_from_app_without_tags(self, app_with_multi_routers):
|
|
1460
|
+
"""Test retrieving route without tags returns first match"""
|
|
1461
|
+
from contentgrid_extension_helpers.responses.hal import get_route_from_app
|
|
1462
|
+
|
|
1463
|
+
# Without tags, should return first match
|
|
1464
|
+
route = get_route_from_app(app_with_multi_routers, "get_bars", tags=None)
|
|
1465
|
+
assert route.path in ["/bar1/bars", "/bar2/bars"]
|
|
1466
|
+
|
|
1467
|
+
def test_get_route_from_app_with_nonexistent_tags(self, app_with_multi_routers):
|
|
1468
|
+
"""Test error when route with specified tags not found"""
|
|
1469
|
+
from contentgrid_extension_helpers.responses.hal import get_route_from_app
|
|
1470
|
+
|
|
1471
|
+
with pytest.raises(ValueError, match="No route found for endpoint get_bars"):
|
|
1472
|
+
get_route_from_app(app_with_multi_routers, "get_bars", tags=["nonexistent"])
|
|
1473
|
+
|
|
1474
|
+
def test_hal_link_for_with_tags(self):
|
|
1475
|
+
"""Test creating HALLinkFor with tags"""
|
|
1476
|
+
link = HALLinkFor(
|
|
1477
|
+
endpoint_function_name="get_bars",
|
|
1478
|
+
tags=["bar1"]
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
assert link.endpoint_function_name == "get_bars"
|
|
1482
|
+
assert link.tags == ["bar1"]
|
|
1483
|
+
|
|
1484
|
+
def test_hal_link_for_tags_default_none(self):
|
|
1485
|
+
"""Test HALLinkFor tags defaults to None"""
|
|
1486
|
+
link = HALLinkFor(endpoint_function_name="get_bars")
|
|
1487
|
+
|
|
1488
|
+
assert link.tags is None
|
|
1489
|
+
|
|
1490
|
+
def test_hal_template_for_with_tags(self):
|
|
1491
|
+
"""Test creating HALTemplateFor with tags"""
|
|
1492
|
+
template = HALTemplateFor(
|
|
1493
|
+
endpoint_function_name="create_bar",
|
|
1494
|
+
tags=["bar1"]
|
|
1495
|
+
)
|
|
1496
|
+
|
|
1497
|
+
assert template.endpoint_function_name == "create_bar"
|
|
1498
|
+
assert template.tags == ["bar1"]
|
|
1499
|
+
|
|
1500
|
+
def test_hal_response_link_expansion_with_tags(self, app_with_multi_routers):
|
|
1501
|
+
"""Test HAL response link expansion uses tags to find correct route"""
|
|
1502
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALResponse, HALLinkFor
|
|
1503
|
+
|
|
1504
|
+
FastAPIHALResponse.init_app(app_with_multi_routers)
|
|
1505
|
+
|
|
1506
|
+
class TestResponse(FastAPIHALResponse):
|
|
1507
|
+
id: int
|
|
1508
|
+
|
|
1509
|
+
# Create response with link targeting specific tag
|
|
1510
|
+
response = TestResponse(
|
|
1511
|
+
id=1,
|
|
1512
|
+
_links={
|
|
1513
|
+
"bars_bar1": HALLinkFor(
|
|
1514
|
+
endpoint_function_name="get_bars",
|
|
1515
|
+
tags=["bar1"]
|
|
1516
|
+
),
|
|
1517
|
+
"bars_bar2": HALLinkFor(
|
|
1518
|
+
endpoint_function_name="get_bars",
|
|
1519
|
+
tags=["bar2"]
|
|
1520
|
+
)
|
|
1521
|
+
}
|
|
1522
|
+
)
|
|
1523
|
+
|
|
1524
|
+
serialized = response.model_dump(by_alias=True)
|
|
1525
|
+
|
|
1526
|
+
assert "/bar1/bars" in serialized["_links"]["bars_bar1"]["href"]
|
|
1527
|
+
assert "/bar2/bars" in serialized["_links"]["bars_bar2"]["href"]
|
|
1528
|
+
|
|
1529
|
+
def test_hal_response_template_expansion_with_tags(self, app_with_multi_routers):
|
|
1530
|
+
"""Test HAL response template expansion uses tags to find correct route"""
|
|
1531
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALResponse, HALTemplateFor
|
|
1532
|
+
|
|
1533
|
+
FastAPIHALResponse.init_app(app_with_multi_routers)
|
|
1534
|
+
|
|
1535
|
+
class TestResponse(FastAPIHALResponse):
|
|
1536
|
+
id: int
|
|
1537
|
+
|
|
1538
|
+
# Create response with templates targeting specific tags
|
|
1539
|
+
response = TestResponse(
|
|
1540
|
+
id=1,
|
|
1541
|
+
_templates={
|
|
1542
|
+
"create_bar1": HALTemplateFor(
|
|
1543
|
+
endpoint_function_name="create_bar",
|
|
1544
|
+
tags=["bar1"]
|
|
1545
|
+
),
|
|
1546
|
+
"create_bar2": HALTemplateFor(
|
|
1547
|
+
endpoint_function_name="create_bar",
|
|
1548
|
+
tags=["bar2"]
|
|
1549
|
+
)
|
|
1550
|
+
}
|
|
1551
|
+
)
|
|
1552
|
+
|
|
1553
|
+
serialized = response.model_dump(by_alias=True)
|
|
1554
|
+
|
|
1555
|
+
assert "_templates" in serialized
|
|
1556
|
+
assert "create_bar1" in serialized["_templates"]
|
|
1557
|
+
assert "create_bar2" in serialized["_templates"]
|
|
1558
|
+
assert "/bar1/bars" in serialized["_templates"]["create_bar1"]["target"]
|
|
1559
|
+
assert "/bar2/bars" in serialized["_templates"]["create_bar2"]["target"]
|
|
1560
|
+
|
|
1561
|
+
def test_hal_collection_with_tags(self, app_with_multi_routers):
|
|
1562
|
+
"""Test HAL collection with tags in embedded resources"""
|
|
1563
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALCollection, HALLinkFor
|
|
1564
|
+
from server.types.bar import Bar
|
|
1565
|
+
|
|
1566
|
+
FastAPIHALResponse.init_app(app_with_multi_routers)
|
|
1567
|
+
|
|
1568
|
+
# Create bars with different tags
|
|
1569
|
+
bar1 = Bar(tags=["bar1"], name="Bar 1", location="Location 1")
|
|
1570
|
+
bar2 = Bar(tags=["bar2"], name="Bar 2", location="Location 2")
|
|
1571
|
+
|
|
1572
|
+
collection = FastAPIHALCollection[Bar](
|
|
1573
|
+
total=2,
|
|
1574
|
+
_links={
|
|
1575
|
+
"self": HALLinkFor(
|
|
1576
|
+
endpoint_function_name="get_bars",
|
|
1577
|
+
tags=["bar1"]
|
|
1578
|
+
)
|
|
1579
|
+
},
|
|
1580
|
+
_embedded={
|
|
1581
|
+
"items": [bar1, bar2]
|
|
1582
|
+
}
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1585
|
+
serialized = collection.model_dump(by_alias=True)
|
|
1586
|
+
|
|
1587
|
+
assert "/bar1/bars" in serialized["_links"]["self"]["href"]
|
|
1588
|
+
assert len(serialized["_embedded"]["items"]) == 2
|
|
1589
|
+
# Check that embedded items also have correct links
|
|
1590
|
+
assert "/bar1/bars" in serialized["_embedded"]["items"][0]["_links"]["bars"]["href"]
|
|
1591
|
+
assert "/bar2/bars" in serialized["_embedded"]["items"][1]["_links"]["bars"]["href"]
|
|
1592
|
+
|
|
1593
|
+
def test_tags_with_enum(self):
|
|
1594
|
+
"""Test using Enum values for tags"""
|
|
1595
|
+
from enum import Enum
|
|
1596
|
+
from contentgrid_extension_helpers.responses.hal import HALLinkFor
|
|
1597
|
+
|
|
1598
|
+
class ApiTags(Enum):
|
|
1599
|
+
BAR1 = "bar1"
|
|
1600
|
+
BAR2 = "bar2"
|
|
1601
|
+
|
|
1602
|
+
link = HALLinkFor(
|
|
1603
|
+
endpoint_function_name="get_bars",
|
|
1604
|
+
tags=[ApiTags.BAR1]
|
|
1605
|
+
)
|
|
1606
|
+
|
|
1607
|
+
assert link.tags == [ApiTags.BAR1]
|
|
1608
|
+
|
|
1609
|
+
def test_mixed_string_and_enum_tags(self):
|
|
1610
|
+
"""Test using mix of string and Enum values for tags"""
|
|
1611
|
+
from enum import Enum
|
|
1612
|
+
from contentgrid_extension_helpers.responses.hal import HALLinkFor
|
|
1613
|
+
|
|
1614
|
+
class ApiTags(Enum):
|
|
1615
|
+
BAR1 = "bar1"
|
|
1616
|
+
|
|
1617
|
+
link = HALLinkFor(
|
|
1618
|
+
endpoint_function_name="get_bars",
|
|
1619
|
+
tags=[ApiTags.BAR1, "bar2"]
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
assert link.tags == [ApiTags.BAR1, "bar2"]
|
|
1623
|
+
|
|
1624
|
+
|
|
1625
|
+
class TestTagsIntegration:
|
|
1626
|
+
"""Integration tests for tags functionality with FastAPI"""
|
|
1627
|
+
|
|
1628
|
+
def test_multi_router_endpoint_integration(self, app_with_multi_routers, client_multi_routers):
|
|
1629
|
+
"""Test multi-router setup returns correct links with tags"""
|
|
1630
|
+
# GET /bar1/bars should return links to bar1 endpoints
|
|
1631
|
+
response = client_multi_routers.get("/bar1/bars")
|
|
1632
|
+
assert response.status_code == 200
|
|
1633
|
+
|
|
1634
|
+
data = response.json()
|
|
1635
|
+
assert "_links" in data
|
|
1636
|
+
assert "self" in data["_links"]
|
|
1637
|
+
assert "/bar1/bars" in data["_links"]["self"]["href"]
|
|
1638
|
+
|
|
1639
|
+
assert "_templates" in data
|
|
1640
|
+
assert "create_bar" in data["_templates"]
|
|
1641
|
+
assert "/bar1/bars" in data["_templates"]["create_bar"]["target"]
|
|
1642
|
+
|
|
1643
|
+
def test_multi_router_second_endpoint_integration(self, app_with_multi_routers, client_multi_routers):
|
|
1644
|
+
"""Test second multi-router endpoint returns correct links"""
|
|
1645
|
+
# GET /bar2/bars should return links to bar2 endpoints
|
|
1646
|
+
response = client_multi_routers.get("/bar2/bars")
|
|
1647
|
+
assert response.status_code == 200
|
|
1648
|
+
|
|
1649
|
+
data = response.json()
|
|
1650
|
+
assert "_links" in data
|
|
1651
|
+
assert "self" in data["_links"]
|
|
1652
|
+
assert "/bar2/bars" in data["_links"]["self"]["href"]
|
|
1653
|
+
|
|
1654
|
+
assert "_templates" in data
|
|
1655
|
+
assert "create_bar" in data["_templates"]
|
|
1656
|
+
assert "/bar2/bars" in data["_templates"]["create_bar"]["target"]
|
|
1657
|
+
|
|
1658
|
+
def test_create_bar_with_tags(self, app_with_multi_routers, client_multi_routers):
|
|
1659
|
+
"""Test creating a bar through tagged endpoint"""
|
|
1660
|
+
bar_data = {
|
|
1661
|
+
"name": "Test Bar",
|
|
1662
|
+
"location": "Test Location",
|
|
1663
|
+
"secret_drink_recipe": "Secret Recipe"
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
response = client_multi_routers.post("/bar1/bars", json=bar_data)
|
|
1667
|
+
assert response.status_code == 200
|
|
1668
|
+
|
|
1669
|
+
data = response.json()
|
|
1670
|
+
assert data["name"] == "Test Bar"
|
|
1671
|
+
assert data["location"] == "Test Location"
|
|
1672
|
+
assert "_links" in data
|
|
1673
|
+
assert "bars" in data["_links"]
|
|
1674
|
+
assert "/bar1/bars" in data["_links"]["bars"]["href"]
|
|
1675
|
+
|
|
1676
|
+
def test_tags_prevent_cross_router_links(self, app_with_multi_routers):
|
|
1677
|
+
"""Test that tags prevent linking to wrong router"""
|
|
1678
|
+
from contentgrid_extension_helpers.responses.hal import FastAPIHALResponse, HALLinkFor
|
|
1679
|
+
|
|
1680
|
+
FastAPIHALResponse.init_app(app_with_multi_routers)
|
|
1681
|
+
|
|
1682
|
+
class TestResponse(FastAPIHALResponse):
|
|
1683
|
+
id: int
|
|
1684
|
+
|
|
1685
|
+
# Try to create link with wrong tag
|
|
1686
|
+
response = TestResponse(
|
|
1687
|
+
id=1,
|
|
1688
|
+
_links={
|
|
1689
|
+
"wrong": HALLinkFor(
|
|
1690
|
+
endpoint_function_name="get_bars",
|
|
1691
|
+
tags=["nonexistent"]
|
|
1692
|
+
)
|
|
1693
|
+
}
|
|
1694
|
+
)
|
|
1695
|
+
|
|
1696
|
+
# Should raise error during serialization
|
|
1697
|
+
with pytest.raises(ValueError, match="No route found for endpoint get_bars"):
|
|
1698
|
+
response.model_dump(by_alias=True)
|
|
1699
|
+
|
|
1700
|
+
|
|
1445
1701
|
if __name__ == "__main__":
|
|
1446
|
-
pytest.main([__file__])
|
|
1702
|
+
pytest.main([__file__])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_foo_router.py
RENAMED
|
File without changes
|
{contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_logging.py
RENAMED
|
File without changes
|
{contentgrid_extension_helpers-0.0.3 → contentgrid_extension_helpers-0.0.4}/tests/test_sqlalch_db.py
RENAMED
|
File without changes
|
|
File without changes
|