faststream_fastapi 1.0.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.
@@ -0,0 +1,5 @@
1
+ from importlib.metadata import version
2
+
3
+ __version__ = version("faststream_fastapi")
4
+
5
+ SERVICE_NAME = f"faststream_fastapi-{__version__}"
@@ -0,0 +1,11 @@
1
+ from faststream_fastapi.asyncapi_config import AsyncAPIConfig
2
+ from faststream_fastapi.context import Context
3
+ from faststream_fastapi.faststream_api import FastStreamAPI
4
+ from faststream_fastapi.stream_message import StreamMessage
5
+
6
+ __all__ = (
7
+ "AsyncAPIConfig",
8
+ "Context",
9
+ "FastStreamAPI",
10
+ "StreamMessage",
11
+ )
File without changes
@@ -0,0 +1,115 @@
1
+ import json
2
+ from collections.abc import Awaitable, Callable, Sequence
3
+ from typing import Any
4
+
5
+ from fastapi import APIRouter, Request, Response
6
+ from fastapi.responses import HTMLResponse, JSONResponse
7
+ from faststream.asgi.factories.asyncapi.try_it_out import TryItOutProcessor
8
+ from faststream.specification.asyncapi.site import get_asyncapi_html
9
+ from faststream.specification.base import SpecificationFactory
10
+
11
+ from faststream_fastapi._internal.fs_re_exports.broker import BrokerUsecase
12
+ from faststream_fastapi.asyncapi_config import AsyncAPIConfig
13
+
14
+
15
+ class AsyncAPIRouter(APIRouter):
16
+ def __init__(
17
+ self,
18
+ brokers: Sequence[BrokerUsecase[Any, Any]],
19
+ config: AsyncAPIConfig,
20
+ schema: SpecificationFactory,
21
+ ) -> None:
22
+ super().__init__(include_in_schema=config.include_in_schema)
23
+
24
+ self._config = config
25
+
26
+ try:
27
+ try_processor = TryItOutProcessor(*brokers)
28
+ except ValueError:
29
+ try_processor = None
30
+
31
+ self.get(self._config.path)(self.make_serve_asyncapi_schema(schema))
32
+
33
+ if self._config.asyncapi_json_path is not None:
34
+ self.get(self._config.asyncapi_json_path)(self.make_download_app_json_schema(schema))
35
+
36
+ if self._config.asyncapi_yaml_path is not None:
37
+ self.get(self._config.asyncapi_yaml_path)(self.make_download_app_yaml_schema(schema))
38
+
39
+ if try_processor is not None and self._config.try_it_out_path is not None:
40
+ self.post(self._config.try_it_out_path, include_in_schema=False)(
41
+ self.make_try_asyncapi_schema(try_processor),
42
+ )
43
+
44
+ def make_download_app_json_schema(
45
+ self,
46
+ schema: SpecificationFactory,
47
+ ) -> Callable[[], Awaitable[Response]]:
48
+ async def download_app_json_schema() -> Response:
49
+ return Response(
50
+ content=json.dumps(
51
+ schema.to_specification().to_jsonable(),
52
+ indent=2,
53
+ ),
54
+ headers={"Content-Type": "application/json"},
55
+ )
56
+
57
+ return download_app_json_schema
58
+
59
+ def make_download_app_yaml_schema(
60
+ self,
61
+ schema: SpecificationFactory,
62
+ ) -> Callable[[], Awaitable[Response]]:
63
+ async def download_app_yaml_schema() -> Response:
64
+ return Response(
65
+ content=schema.to_specification().to_yaml(),
66
+ headers={
67
+ "Content-Type": "application/octet-stream",
68
+ },
69
+ )
70
+
71
+ return download_app_yaml_schema
72
+
73
+ def make_serve_asyncapi_schema(
74
+ self,
75
+ schema: SpecificationFactory,
76
+ ) -> Callable[[], Awaitable[Response]]:
77
+ async def serve_asyncapi_schema() -> Response:
78
+ return HTMLResponse(
79
+ content=get_asyncapi_html(
80
+ schema.to_specification(),
81
+ sidebar=self._config.sidebar,
82
+ info=self._config.info,
83
+ servers=self._config.servers,
84
+ operations=self._config.operations,
85
+ messages=self._config.messages,
86
+ schemas=self._config.schemas,
87
+ errors=self._config.errors,
88
+ expand_message_examples=self._config.expand_message_examples,
89
+ asyncapi_js_url=self._config.asyncapi_js_url,
90
+ asyncapi_css_url=self._config.asyncapi_css_url,
91
+ try_it_out_plugin_url=self._config.try_it_out_plugin_url,
92
+ try_it_out_path=self._config.try_it_out_path,
93
+ ),
94
+ )
95
+
96
+ return serve_asyncapi_schema
97
+
98
+ def make_try_asyncapi_schema(
99
+ self,
100
+ try_processor: TryItOutProcessor,
101
+ ) -> Callable[[Request], Awaitable[Response]]:
102
+ async def try_asyncapi_schema(request: Request) -> Response:
103
+ try:
104
+ body = await request.json()
105
+ except Exception as e: # noqa: BLE001
106
+ return JSONResponse({"details": f"Invalid JSON: {e}"}, 400)
107
+
108
+ result = await try_processor.process(body)
109
+ return Response(
110
+ content=result.body,
111
+ status_code=result.status_code,
112
+ media_type="application/json",
113
+ )
114
+
115
+ return try_asyncapi_schema
@@ -0,0 +1,22 @@
1
+ from types import TracebackType
2
+ from typing import cast
3
+
4
+ from fastapi.background import BackgroundTasks
5
+ from faststream.middlewares import BaseMiddleware
6
+
7
+
8
+ class _BackgroundMiddleware(BaseMiddleware):
9
+ async def __aexit__(
10
+ self,
11
+ exc_type: type[BaseException] | None = None,
12
+ exc_val: BaseException | None = None,
13
+ exc_tb: TracebackType | None = None,
14
+ ) -> bool | None:
15
+ background = cast(
16
+ "BackgroundTasks | None",
17
+ getattr(self.context.get_local("message"), "background", None),
18
+ )
19
+ if exc_type is None and background is not None:
20
+ await background()
21
+
22
+ return await super().after_processed(exc_type, exc_val, exc_tb)
@@ -0,0 +1,23 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from fastapi import FastAPI
5
+ from fastapi.datastructures import State
6
+
7
+
8
+ class ConfigAsgiStateError(ValueError):
9
+ def __str__(self) -> str:
10
+ return "ASGI state already is set"
11
+
12
+
13
+ @dataclass
14
+ class Config:
15
+ application: FastAPI
16
+ dependency_overrides_provider: Any | None
17
+ asgi_state: State | None = None
18
+
19
+ def set_asgi_state(self, asgi_state: State) -> None:
20
+ if self.asgi_state is not None:
21
+ raise ConfigAsgiStateError # pragma: no cover
22
+
23
+ self.asgi_state = asgi_state
@@ -0,0 +1,152 @@
1
+ from dataclasses import dataclass
2
+ from typing import Any
3
+
4
+ from fastapi import __version__ as FASTAPI_VERSION # noqa: N812
5
+ from fastapi.dependencies.models import Dependant
6
+ from fastapi.dependencies.utils import solve_dependencies
7
+ from fastapi.requests import Request
8
+ from starlette.background import BackgroundTasks
9
+ from typing_extensions import Never
10
+
11
+ major, minor, patch, *_ = FASTAPI_VERSION.split(".")
12
+
13
+ _FASTAPI_MAJOR, _FASTAPI_MINOR = int(major), int(minor)
14
+
15
+ FASTAPI_V2 = _FASTAPI_MAJOR > 0 or _FASTAPI_MINOR > 100
16
+ FASTAPI_V106 = _FASTAPI_MAJOR > 0 or _FASTAPI_MINOR >= 106
17
+ FASTAPI_V121 = _FASTAPI_MAJOR > 0 or _FASTAPI_MINOR >= 121
18
+ FASTAPI_V128 = _FASTAPI_MAJOR > 0 or _FASTAPI_MINOR >= 128
19
+
20
+ try:
21
+ _FASTAPI_PATCH = int(patch)
22
+ except ValueError:
23
+ FASTAPI_v102_3 = True
24
+ FASTAPI_v102_4 = True
25
+ else:
26
+ FASTAPI_v102_3 = (
27
+ _FASTAPI_MAJOR > 0
28
+ or _FASTAPI_MINOR > 112
29
+ or (_FASTAPI_MINOR == 112 and _FASTAPI_PATCH > 2)
30
+ )
31
+ FASTAPI_v102_4 = (
32
+ _FASTAPI_MAJOR > 0
33
+ or _FASTAPI_MINOR > 112
34
+ or (_FASTAPI_MINOR == 112 and _FASTAPI_PATCH > 3)
35
+ )
36
+
37
+ __all__ = (
38
+ "RequestValidationError",
39
+ "create_response_field",
40
+ "raise_fastapi_validation_error",
41
+ "solve_faststream_dependency",
42
+ )
43
+
44
+
45
+ @dataclass
46
+ class SolvedDependency:
47
+ values: dict[str, Any]
48
+ errors: list[Any]
49
+ background_tasks: BackgroundTasks | None
50
+
51
+
52
+ if FASTAPI_V128:
53
+ from fastapi.exceptions import RequestValidationError
54
+
55
+ def raise_fastapi_validation_error(
56
+ errors: list[Any],
57
+ body: dict[str, Any],
58
+ ) -> Never:
59
+ raise RequestValidationError(errors, body=body)
60
+
61
+ elif FASTAPI_V2:
62
+ from fastapi._compat import _normalize_errors # type: ignore[attr-defined]
63
+ from fastapi.exceptions import RequestValidationError
64
+
65
+ def raise_fastapi_validation_error(
66
+ errors: list[Any],
67
+ body: dict[str, Any],
68
+ ) -> Never:
69
+ raise RequestValidationError(_normalize_errors(errors), body=body)
70
+
71
+ else:
72
+ from pydantic import ( # type: ignore[assignment]
73
+ ValidationError as RequestValidationError,
74
+ )
75
+ from pydantic import (
76
+ create_model,
77
+ )
78
+
79
+ ROUTER_VALIDATION_ERROR_MODEL = create_model("StreamRoute")
80
+
81
+ def raise_fastapi_validation_error(
82
+ errors: list[Any],
83
+ body: dict[str, Any],
84
+ ) -> Never:
85
+ raise RequestValidationError(errors, ROUTER_VALIDATION_ERROR_MODEL) # type: ignore[misc]
86
+
87
+
88
+ if FASTAPI_v102_3:
89
+ from fastapi.utils import (
90
+ create_model_field as create_response_field,
91
+ )
92
+
93
+ extra = {"embed_body_fields": False} if FASTAPI_v102_4 else {}
94
+
95
+ async def solve_faststream_dependency(
96
+ request: Request,
97
+ dependant: Dependant,
98
+ dependency_overrides_provider: Any | None,
99
+ **kwargs: Any,
100
+ ) -> SolvedDependency:
101
+ solved_result = await solve_dependencies(
102
+ request=request,
103
+ body=request._body,
104
+ dependant=dependant,
105
+ dependency_overrides_provider=dependency_overrides_provider,
106
+ **extra, # type: ignore[arg-type]
107
+ **kwargs,
108
+ )
109
+ values, errors, background = (
110
+ solved_result.values,
111
+ solved_result.errors,
112
+ solved_result.background_tasks,
113
+ )
114
+
115
+ return SolvedDependency(
116
+ values=values,
117
+ errors=errors,
118
+ background_tasks=background,
119
+ )
120
+
121
+ else:
122
+ from fastapi.utils import ( # type: ignore[attr-defined,no-redef]
123
+ create_response_field,
124
+ )
125
+
126
+ async def solve_faststream_dependency(
127
+ request: Request,
128
+ dependant: Dependant,
129
+ dependency_overrides_provider: Any | None,
130
+ **kwargs: Any,
131
+ ) -> SolvedDependency:
132
+ solved_result = await solve_dependencies(
133
+ request=request,
134
+ body=request._body,
135
+ dependant=dependant,
136
+ dependency_overrides_provider=dependency_overrides_provider,
137
+ **kwargs,
138
+ )
139
+
140
+ (
141
+ values,
142
+ errors,
143
+ background,
144
+ _response,
145
+ _dependency_cache,
146
+ ) = solved_result # type: ignore[misc]
147
+
148
+ return SolvedDependency(
149
+ values=values, # type: ignore[has-type]
150
+ errors=errors, # type: ignore[has-type]
151
+ background_tasks=background, # type: ignore[has-type]
152
+ )
File without changes
@@ -0,0 +1,6 @@
1
+ from faststream._internal._compat import PYDANTIC_V2, PydanticUndefined
2
+
3
+ __all__ = (
4
+ "PYDANTIC_V2",
5
+ "PydanticUndefined",
6
+ )
@@ -0,0 +1,3 @@
1
+ from faststream._internal.application import StartAbleApplication
2
+
3
+ __all__ = ("StartAbleApplication",)
@@ -0,0 +1,4 @@
1
+ from faststream._internal.broker import BrokerUsecase
2
+ from faststream._internal.broker.router import BrokerRouter
3
+
4
+ __all__ = ("BrokerRouter", "BrokerUsecase")
@@ -0,0 +1,3 @@
1
+ from faststream._internal.constants import EMPTY
2
+
3
+ __all__ = ("EMPTY",)
@@ -0,0 +1,8 @@
1
+ from faststream._internal.context import Context, ContextRepo
2
+ from faststream._internal.context.resolve import resolve_context_by_name
3
+
4
+ __all__ = (
5
+ "Context",
6
+ "ContextRepo",
7
+ "resolve_context_by_name",
8
+ )
@@ -0,0 +1,3 @@
1
+ from faststream._internal.di import FastDependsConfig
2
+
3
+ __all__ = ("FastDependsConfig",)
@@ -0,0 +1,3 @@
1
+ from faststream._internal.logger.logging import get_logger
2
+
3
+ __all__ = ("get_logger",)
@@ -0,0 +1,182 @@
1
+ import inspect
2
+ from collections.abc import Callable, Iterable
3
+ from typing import Annotated, Any, Final, cast, get_args, get_origin
4
+
5
+ from fast_depends.library.serializer import OptionItem
6
+ from fast_depends.utils import get_typed_annotation
7
+ from fastapi.dependencies.models import Dependant
8
+ from fastapi.dependencies.utils import (
9
+ get_dependant,
10
+ get_parameterless_sub_dependant,
11
+ get_typed_signature,
12
+ )
13
+ from fastapi.params import Depends
14
+ from pydantic import Field, create_model
15
+
16
+ from faststream_fastapi._internal.fs_re_exports._compat import PYDANTIC_V2, PydanticUndefined
17
+
18
+
19
+ def get_fastapi_dependant(
20
+ orig_call: Callable[..., Any],
21
+ dependencies: Iterable[Depends],
22
+ ) -> Dependant:
23
+ dependent = get_fastapi_native_dependant(orig_call=orig_call, dependencies=dependencies)
24
+ return _patch_fastapi_dependent(dependent)
25
+
26
+
27
+ def get_fastapi_native_dependant(
28
+ orig_call: Callable[..., Any],
29
+ dependencies: Iterable[Depends],
30
+ ) -> Dependant:
31
+ dependent = get_dependant(
32
+ path="",
33
+ call=orig_call,
34
+ )
35
+
36
+ for depends in list(dependencies)[::-1]:
37
+ dependent.dependencies.insert(
38
+ 0,
39
+ get_parameterless_sub_dependant(depends=depends, path=""),
40
+ )
41
+
42
+ return dependent
43
+
44
+
45
+ def _patch_fastapi_dependent(dependant: Dependant) -> Dependant:
46
+ params = dependant.query_params + dependant.body_params
47
+
48
+ for d in dependant.dependencies:
49
+ params.extend(d.query_params + d.body_params)
50
+
51
+ params_unique = {}
52
+
53
+ call = dependant.call
54
+ if is_faststream_decorated(call):
55
+ call = getattr(call, "__wrapped__", call)
56
+ globalns = getattr(call, "__globals__", {})
57
+
58
+ for p in params:
59
+ if p.name not in params_unique:
60
+ info: Any = p.field_info if PYDANTIC_V2 else p
61
+
62
+ field_data = {
63
+ "default": ... if info.default is PydanticUndefined else info.default,
64
+ "default_factory": info.default_factory,
65
+ "alias": info.alias,
66
+ }
67
+
68
+ if PYDANTIC_V2:
69
+ from pydantic.fields import FieldInfo # noqa: PLC0415
70
+
71
+ info = cast("FieldInfo", info)
72
+
73
+ field_data.update(
74
+ {
75
+ "title": info.title,
76
+ "alias_priority": info.alias_priority,
77
+ "validation_alias": info.validation_alias,
78
+ "serialization_alias": info.serialization_alias,
79
+ "description": info.description,
80
+ "discriminator": info.discriminator,
81
+ "examples": info.examples,
82
+ "exclude": info.exclude,
83
+ "json_schema_extra": info.json_schema_extra,
84
+ },
85
+ )
86
+
87
+ f = next(
88
+ filter(
89
+ lambda x: isinstance(x, FieldInfo),
90
+ p.field_info.metadata or (),
91
+ ),
92
+ Field(**field_data), # type: ignore[pydantic-field,unused-ignore]
93
+ )
94
+
95
+ else:
96
+ from pydantic.fields import ( # type: ignore[attr-defined] # noqa: PLC0415 # pragma: no cover
97
+ ModelField,
98
+ )
99
+
100
+ info = cast("ModelField", info)
101
+
102
+ field_data.update(
103
+ {
104
+ "title": info.field_info.title,
105
+ "description": info.field_info.description,
106
+ "discriminator": info.field_info.discriminator,
107
+ "exclude": info.field_info.exclude,
108
+ "gt": info.field_info.gt,
109
+ "ge": info.field_info.ge,
110
+ "lt": info.field_info.lt,
111
+ "le": info.field_info.le,
112
+ },
113
+ )
114
+ f = Field(**field_data) # type: ignore[pydantic-field,unused-ignore]
115
+
116
+ params_unique[p.name] = (
117
+ get_typed_annotation(info.annotation, globalns, {}),
118
+ f,
119
+ )
120
+
121
+ dependant.model = create_model( # type: ignore[attr-defined]
122
+ getattr(call, "__name__", type(call).__name__),
123
+ )
124
+
125
+ dependant.custom_fields = {} # type: ignore[attr-defined]
126
+ dependant.flat_params = [ # type: ignore[attr-defined]
127
+ OptionItem(field_name=name, field_type=type_, default_value=default)
128
+ for name, (type_, default) in params_unique.items()
129
+ ]
130
+
131
+ return dependant
132
+
133
+
134
+ def has_forbidden_types( # noqa: C901
135
+ orig_call: Callable[..., Any],
136
+ forbidden_types: tuple[Any, ...],
137
+ ) -> set[Any]:
138
+ endpoint_signature = get_typed_signature(orig_call)
139
+ signature_params = endpoint_signature.parameters
140
+
141
+ founded_types = set()
142
+
143
+ for param in signature_params.values():
144
+ ann = param.annotation
145
+
146
+ founded_buffer = set()
147
+ has_fastapi_depends = False
148
+ if ann is not inspect.Signature.empty and get_origin(ann) is Annotated:
149
+ annotated_args = get_args(ann)
150
+
151
+ for arg in annotated_args[1:]:
152
+ if isinstance(arg, Depends):
153
+ has_fastapi_depends = True
154
+ continue
155
+
156
+ for t in forbidden_types:
157
+ if isinstance(arg, t):
158
+ founded_buffer.add(t)
159
+
160
+ if isinstance(param.default, Depends):
161
+ has_fastapi_depends = True
162
+ continue
163
+
164
+ for t in forbidden_types:
165
+ if isinstance(param.default, t):
166
+ founded_buffer.add(t)
167
+
168
+ if not has_fastapi_depends:
169
+ founded_types |= founded_buffer
170
+
171
+ return founded_types
172
+
173
+
174
+ FASTSTREAM_FASTAPI_PLUGIN_DECORATOR_MARKER: Final = "__faststream_consumer__"
175
+
176
+
177
+ def is_faststream_decorated(func: object) -> bool:
178
+ return getattr(func, FASTSTREAM_FASTAPI_PLUGIN_DECORATOR_MARKER, False)
179
+
180
+
181
+ def mark_faststream_decorated(func: object) -> None:
182
+ setattr(func, FASTSTREAM_FASTAPI_PLUGIN_DECORATOR_MARKER, True)
@@ -0,0 +1,11 @@
1
+ import logging
2
+ import sys
3
+
4
+ from faststream_fastapi._internal.fs_re_exports.logger import get_logger
5
+
6
+ logger = get_logger(
7
+ name="faststream_fastapi",
8
+ log_level=logging.INFO,
9
+ stream=sys.stderr,
10
+ fmt="%(asctime)s %(levelname)8s - %(message)s",
11
+ )
@@ -0,0 +1,119 @@
1
+ import functools
2
+ import inspect
3
+ from collections.abc import Callable, Mapping
4
+ from typing import (
5
+ Annotated,
6
+ Any,
7
+ ParamSpec,
8
+ TypeVar,
9
+ cast,
10
+ get_args,
11
+ get_origin,
12
+ get_type_hints,
13
+ )
14
+
15
+ from fastapi.dependencies.utils import get_typed_signature
16
+ from faststream._internal.context import Context as ContextCls
17
+
18
+ from faststream_fastapi import Context as PluginContext
19
+
20
+ P_HandlerParams = ParamSpec("P_HandlerParams")
21
+ T_HandlerReturn = TypeVar("T_HandlerReturn")
22
+
23
+
24
+ def replace_context(
25
+ func: Callable[P_HandlerParams, T_HandlerReturn],
26
+ ) -> Callable[P_HandlerParams, T_HandlerReturn]:
27
+ if inspect.iscoroutinefunction(func):
28
+
29
+ @functools.wraps(func)
30
+ async def wrapper(*args: Any, **kwargs: Any) -> Any:
31
+ return await func(*args, **kwargs)
32
+ else:
33
+
34
+ @functools.wraps(func)
35
+ def wrapper(*args: Any, **kwargs: Any) -> Any:
36
+ return func(*args, **kwargs)
37
+
38
+ endpoint_signature = get_typed_signature(func)
39
+ new_params = get_new_parameters(endpoint_signature.parameters)
40
+ new_annotations = get_new_annotations(get_type_hints(func, include_extras=True))
41
+
42
+ wrapper.__name__ = func.__name__
43
+ wrapper.__qualname__ = func.__qualname__
44
+ wrapper.__doc__ = func.__doc__
45
+ wrapper.__module__ = func.__module__
46
+ wrapper.__annotations__ = new_annotations
47
+ wrapper.__signature__ = inspect.Signature( # type: ignore[attr-defined]
48
+ parameters=new_params,
49
+ return_annotation=endpoint_signature.return_annotation,
50
+ )
51
+
52
+ return cast(Callable[P_HandlerParams, T_HandlerReturn], wrapper)
53
+
54
+
55
+ def get_new_parameters(
56
+ signature_params: Mapping[str, inspect.Parameter],
57
+ ) -> list[inspect.Parameter]:
58
+ new_params = []
59
+
60
+ for param in signature_params.values():
61
+ if (
62
+ param.annotation is not inspect.Signature.empty
63
+ and get_origin(param.annotation) is Annotated
64
+ ):
65
+ new_params.append(
66
+ inspect.Parameter(
67
+ name=param.name,
68
+ default=param.default,
69
+ kind=param.kind,
70
+ annotation=replace_annotated(param.annotation, param.name),
71
+ ),
72
+ )
73
+ elif isinstance(param.default, ContextCls):
74
+ new_param = inspect.Parameter(
75
+ name=param.name,
76
+ default=create_plugin_context(param.default, param.name),
77
+ kind=param.kind,
78
+ annotation=param.annotation,
79
+ )
80
+ new_params.append(new_param)
81
+ else:
82
+ new_params.append(param)
83
+
84
+ return new_params
85
+
86
+
87
+ def get_new_annotations(type_hints: Mapping[str, Any]) -> dict[str, Any]:
88
+ new_annotations = {}
89
+
90
+ for param_name, type_hint in type_hints.items():
91
+ if get_origin(type_hint) is Annotated:
92
+ new_annotations[param_name] = replace_annotated(type_hint, param_name)
93
+ else:
94
+ new_annotations[param_name] = type_hint
95
+
96
+ return new_annotations
97
+
98
+
99
+ def replace_annotated(type_hint: Any, parameter_name: str) -> Any:
100
+ annotated_args = get_args(type_hint)
101
+
102
+ new_annotated_args = [annotated_args[0]]
103
+ for arg in annotated_args[1:]:
104
+ if isinstance(arg, ContextCls):
105
+ new_annotated_args.append(create_plugin_context(arg, parameter_name))
106
+ else:
107
+ new_annotated_args.append(arg)
108
+
109
+ return Annotated[tuple(new_annotated_args)]
110
+
111
+
112
+ def create_plugin_context(fs_context: ContextCls, parameter_name: str) -> Any:
113
+ fs_context.set_param_name(parameter_name)
114
+
115
+ return PluginContext(
116
+ name=fs_context.name or fs_context.param_name,
117
+ default=fs_context.default,
118
+ initial=fs_context.initial,
119
+ )