Skip to content

Commit 2ba70ad

Browse files
authored
Merge pull request #313 from Ivan-Feofanov/optional-unique-operation-id-suffix
Optional unique operation ID suffixes
2 parents 4896478 + c293f0b commit 2ba70ad

File tree

3 files changed

+67
-2
lines changed

3 files changed

+67
-2
lines changed

docs/api_controller/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ The fourth argument, `permissions`, is a list of all permissions that should be
4343

4444
The fifth argument, `auto_import`, defaults to true, which automatically adds your controller to auto import list.
4545

46-
The fifth argument, `urls_namespace`, defaults to `None`, or if set will nest the routes of this controller under their own namespace.
46+
The sixth argument, `urls_namespace`, defaults to `Controller Class Name`, or if set will nest the routes of this controller under their own namespace.
47+
48+
The seventh argument, `append_unique_op_id`, defaults to true, which appends a unique operation id suffix to the controller's routes to avoid conflicts.
4749

4850
for example:
4951

ninja_extra/controllers/base.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ def __init__(
404404
permissions: Optional[List[BasePermissionType]] = None,
405405
auto_import: bool = True,
406406
urls_namespace: Optional[str] = None,
407+
append_unique_op_id: bool = True
407408
) -> None:
408409
self.prefix = prefix
409410
# Optional controller-level URL namespace. Applied to all route paths.
@@ -413,6 +414,7 @@ def __init__(
413414

414415
self.tags = tags # type: ignore
415416
self.throttle = throttle
417+
self.append_unique_op_id = append_unique_op_id
416418

417419
self.auto_import: bool = auto_import # set to false and it would be ignored when api.auto_discover is called
418420
# `controller_class` target class that the APIController wraps
@@ -597,7 +599,13 @@ def _add_operation_from_route_function(self, route_function: RouteFunction) -> N
597599
controller_name = (
598600
str(self.controller_class.__name__).lower().replace("controller", "")
599601
)
600-
route_function.route.route_params.operation_id = f"{controller_name}_{route_function.route.view_func.__name__}_{str(uuid.uuid4())[:8]}"
602+
route_function.route.route_params.operation_id = (
603+
f"{controller_name}_{route_function.route.view_func.__name__}"
604+
)
605+
if self.append_unique_op_id:
606+
route_function.route.route_params.operation_id += (
607+
f"_{uuid.uuid4().hex[:8]}"
608+
)
601609

602610
if (
603611
self.auth
@@ -687,6 +695,7 @@ def api_controller(
687695
permissions: Optional[List[BasePermissionType]] = None,
688696
auto_import: bool = True,
689697
urls_namespace: Optional[str] = None,
698+
append_unique_op_id: bool = True,
690699
) -> Callable[
691700
[Union[Type, Type[T]]], Union[Type[ControllerBase], Type[T]]
692701
]: # pragma: no cover
@@ -701,6 +710,7 @@ def api_controller(
701710
permissions: Optional[List[BasePermissionType]] = None,
702711
auto_import: bool = True,
703712
urls_namespace: Optional[str] = None,
713+
append_unique_op_id: bool = True,
704714
) -> Union[ControllerClassType, Callable[[ControllerClassType], ControllerClassType]]:
705715
if isinstance(prefix_or_class, type):
706716
return APIController(
@@ -710,6 +720,7 @@ def api_controller(
710720
permissions=permissions,
711721
auto_import=auto_import,
712722
throttle=throttle,
723+
append_unique_op_id=append_unique_op_id,
713724
urls_namespace=urls_namespace,
714725
)(prefix_or_class)
715726

@@ -721,6 +732,7 @@ def _decorator(cls: ControllerClassType) -> ControllerClassType:
721732
permissions=permissions,
722733
auto_import=auto_import,
723734
throttle=throttle,
735+
append_unique_op_id=append_unique_op_id,
724736
urls_namespace=urls_namespace,
725737
)(cls)
726738

tests/test_controller.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ class DisableAutoImportController:
6868
auto_import = False # disable auto_import of the controller
6969

7070

71+
@api_controller
72+
class SomeControllerWithSingleRoute:
73+
@http_get("/example")
74+
def example(self):
75+
pass
76+
77+
78+
@api_controller(append_unique_op_id=False)
79+
class SomeControllerWithoutUniqueSuffix:
80+
@http_get("/example")
81+
def example(self):
82+
pass
83+
84+
7185
class TestAPIController:
7286
def test_api_controller_as_decorator(self):
7387
controller_type = api_controller("prefix", tags="new_tag", auth=FakeAuth())(
@@ -144,6 +158,43 @@ def test_controller_should_have_path_operation_list(self):
144158
assert operation.methods == route_function.route.route_params.methods
145159
assert operation.operation_id == route_function.route.route_params.operation_id
146160

161+
def test_controller_should_append_unique_op_id_to_operation_id(self):
162+
_api_controller = SomeControllerWithSingleRoute.get_api_controller()
163+
controller_name = (
164+
str(_api_controller.controller_class.__name__)
165+
.lower()
166+
.replace("controller", "")
167+
)
168+
route_view_func_name: RouteFunction = get_route_function(
169+
SomeControllerWithRoute().example
170+
).route.view_func.__name__
171+
172+
operation_id = (
173+
_api_controller._path_operations.get("/example").operations[0].operation_id
174+
)
175+
raw_operation_id = "_".join(operation_id.split("_")[:-1])
176+
op_id_postfix = operation_id.split("_")[-1]
177+
178+
assert raw_operation_id == f"{controller_name}_{route_view_func_name}"
179+
assert len(op_id_postfix) == 8
180+
181+
def test_controller_should_not_add_unique_suffix_following_params(self):
182+
_api_controller = SomeControllerWithoutUniqueSuffix.get_api_controller()
183+
controller_name = (
184+
str(_api_controller.controller_class.__name__)
185+
.lower()
186+
.replace("controller", "")
187+
)
188+
route_view_func_name: RouteFunction = get_route_function(
189+
SomeControllerWithRoute().example
190+
).route.view_func.__name__
191+
192+
operation_id = (
193+
_api_controller._path_operations.get("/example").operations[0].operation_id
194+
)
195+
196+
assert operation_id == f"{controller_name}_{route_view_func_name}"
197+
147198
def test_get_route_function_should_return_instance_route_definitions(self):
148199
for route_definition in get_route_functions(SomeControllerWithRoute):
149200
assert isinstance(route_definition, RouteFunction)

0 commit comments

Comments
 (0)