dapr-ext-fastapi-dev 1.9.0rc1.dev1402__tar.gz → 1.16.0.dev59__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.
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/PKG-INFO +15 -7
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/__init__.py +2 -6
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/actor.py +47 -62
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/app.py +35 -28
- dapr_ext_fastapi_dev-1.16.0.dev59/dapr/ext/fastapi/py.typed +0 -0
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/version.py +2 -2
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr_ext_fastapi_dev.egg-info/PKG-INFO +15 -7
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr_ext_fastapi_dev.egg-info/SOURCES.txt +4 -1
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr_ext_fastapi_dev.egg-info/requires.txt +1 -1
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/setup.cfg +9 -4
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/setup.py +3 -3
- dapr_ext_fastapi_dev-1.16.0.dev59/tests/test_app.py +156 -0
- dapr_ext_fastapi_dev-1.16.0.dev59/tests/test_dapractor.py +88 -0
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/LICENSE +0 -0
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/README.rst +0 -0
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr_ext_fastapi_dev.egg-info/dependency_links.txt +0 -0
- {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr_ext_fastapi_dev.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version:
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dapr-ext-fastapi-dev
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.16.0.dev59
|
|
4
4
|
Summary: The developmental release for Dapr FastAPI extension.
|
|
5
5
|
Home-page: https://dapr.io/
|
|
6
6
|
Author: Dapr Authors
|
|
@@ -8,15 +8,23 @@ Author-email: daprweb@microsoft.com
|
|
|
8
8
|
License: Apache
|
|
9
9
|
Project-URL: Documentation, https://github.com/dapr/docs
|
|
10
10
|
Project-URL: Source, https://github.com/dapr/python-sdk
|
|
11
|
-
Description: This is the developmental release for Dapr FastAPI extension.
|
|
12
|
-
Platform: UNKNOWN
|
|
13
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
12
|
Classifier: Intended Audience :: Developers
|
|
15
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
14
|
Classifier: Operating System :: OS Independent
|
|
17
15
|
Classifier: Programming Language :: Python
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: dapr>=1.16.0.dev
|
|
24
|
+
Requires-Dist: uvicorn>=0.11.6
|
|
25
|
+
Requires-Dist: fastapi>=0.60.1
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
This is the developmental release for Dapr FastAPI extension.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
Copyright
|
|
4
|
+
Copyright 2023 The Dapr Authors
|
|
5
5
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
you may not use this file except in compliance with the License.
|
|
7
7
|
You may obtain a copy of the License at
|
|
@@ -16,8 +16,4 @@ limitations under the License.
|
|
|
16
16
|
from .actor import DaprActor
|
|
17
17
|
from .app import DaprApp
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
__all__ = [
|
|
21
|
-
'DaprActor',
|
|
22
|
-
'DaprApp'
|
|
23
|
-
]
|
|
19
|
+
__all__ = ['DaprActor', 'DaprApp']
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
Copyright
|
|
4
|
+
Copyright 2023 The Dapr Authors
|
|
5
5
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
you may not use this file except in compliance with the License.
|
|
7
7
|
You may obtain a copy of the License at
|
|
@@ -13,25 +13,25 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
from typing import Any, Optional, Type
|
|
17
|
-
|
|
18
|
-
from fastapi import FastAPI, APIRouter, Request, Response, status # type: ignore
|
|
19
|
-
from fastapi.logger import logger
|
|
20
|
-
from fastapi.responses import JSONResponse
|
|
16
|
+
from typing import Any, List, Optional, Type
|
|
21
17
|
|
|
22
18
|
from dapr.actor import Actor, ActorRuntime
|
|
23
|
-
from dapr.clients.exceptions import
|
|
19
|
+
from dapr.clients.exceptions import ERROR_CODE_UNKNOWN, DaprInternalError
|
|
24
20
|
from dapr.serializers import DefaultJSONSerializer
|
|
21
|
+
from fastapi import APIRouter, FastAPI, Request, Response, status # type: ignore
|
|
22
|
+
from fastapi.logger import logger
|
|
23
|
+
from fastapi.responses import JSONResponse
|
|
25
24
|
|
|
26
|
-
DEFAULT_CONTENT_TYPE =
|
|
25
|
+
DEFAULT_CONTENT_TYPE = 'application/json; utf-8'
|
|
27
26
|
DAPR_REENTRANCY_ID_HEADER = 'Dapr-Reentrancy-Id'
|
|
28
27
|
|
|
29
28
|
|
|
30
29
|
def _wrap_response(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
status_code: int,
|
|
31
|
+
msg: Any,
|
|
32
|
+
error_code: Optional[str] = None,
|
|
33
|
+
content_type: Optional[str] = DEFAULT_CONTENT_TYPE,
|
|
34
|
+
):
|
|
35
35
|
resp = None
|
|
36
36
|
if isinstance(msg, str):
|
|
37
37
|
response_obj = {
|
|
@@ -48,9 +48,7 @@ def _wrap_response(
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
class DaprActor(object):
|
|
51
|
-
|
|
52
|
-
def __init__(self, app: FastAPI,
|
|
53
|
-
router_tags: Optional[List[str]] = ['Actor']):
|
|
51
|
+
def __init__(self, app: FastAPI, router_tags: Optional[List[str]] = ['Actor']):
|
|
54
52
|
# router_tags should be added to all magic Dapr Actor methods implemented here
|
|
55
53
|
self._router_tags = router_tags
|
|
56
54
|
self._router = APIRouter()
|
|
@@ -59,7 +57,7 @@ class DaprActor(object):
|
|
|
59
57
|
app.include_router(self._router)
|
|
60
58
|
|
|
61
59
|
def init_routes(self, router: APIRouter):
|
|
62
|
-
@router.get(
|
|
60
|
+
@router.get('/healthz', tags=self._router_tags)
|
|
63
61
|
async def healthz():
|
|
64
62
|
return {'status': 'ok'}
|
|
65
63
|
|
|
@@ -73,96 +71,83 @@ class DaprActor(object):
|
|
|
73
71
|
try:
|
|
74
72
|
await ActorRuntime.deactivate(actor_type_name, actor_id)
|
|
75
73
|
except DaprInternalError as ex:
|
|
76
|
-
return _wrap_response(
|
|
77
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
78
|
-
ex.as_dict())
|
|
74
|
+
return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_json_safe_dict())
|
|
79
75
|
except Exception as ex:
|
|
80
76
|
return _wrap_response(
|
|
81
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
82
|
-
|
|
83
|
-
ERROR_CODE_UNKNOWN)
|
|
77
|
+
status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN
|
|
78
|
+
)
|
|
84
79
|
|
|
85
80
|
msg = f'deactivated actor: {actor_type_name}.{actor_id}'
|
|
86
81
|
logger.debug(msg)
|
|
87
82
|
return _wrap_response(status.HTTP_200_OK, msg)
|
|
88
83
|
|
|
89
|
-
@router.put(
|
|
90
|
-
|
|
84
|
+
@router.put(
|
|
85
|
+
'/actors/{actor_type_name}/{actor_id}/method/{method_name}', tags=self._router_tags
|
|
86
|
+
)
|
|
91
87
|
async def actor_method(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
method_name: str,
|
|
95
|
-
request: Request):
|
|
88
|
+
actor_type_name: str, actor_id: str, method_name: str, request: Request
|
|
89
|
+
):
|
|
96
90
|
try:
|
|
97
91
|
# Read raw bytes from request stream
|
|
98
92
|
req_body = await request.body()
|
|
99
93
|
reentrancy_id = request.headers.get(DAPR_REENTRANCY_ID_HEADER)
|
|
100
94
|
result = await ActorRuntime.dispatch(
|
|
101
|
-
actor_type_name, actor_id, method_name, req_body, reentrancy_id
|
|
95
|
+
actor_type_name, actor_id, method_name, req_body, reentrancy_id
|
|
96
|
+
)
|
|
102
97
|
except DaprInternalError as ex:
|
|
103
|
-
return _wrap_response(
|
|
104
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_dict())
|
|
98
|
+
return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_json_safe_dict())
|
|
105
99
|
except Exception as ex:
|
|
106
100
|
return _wrap_response(
|
|
107
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
108
|
-
|
|
109
|
-
ERROR_CODE_UNKNOWN)
|
|
101
|
+
status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN
|
|
102
|
+
)
|
|
110
103
|
|
|
111
104
|
msg = f'called method. actor: {actor_type_name}.{actor_id}, method: {method_name}'
|
|
112
105
|
logger.debug(msg)
|
|
113
106
|
return _wrap_response(status.HTTP_200_OK, result)
|
|
114
107
|
|
|
115
|
-
@router.put(
|
|
116
|
-
|
|
108
|
+
@router.put(
|
|
109
|
+
'/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}', tags=self._router_tags
|
|
110
|
+
)
|
|
117
111
|
async def actor_timer(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
timer_name: str,
|
|
121
|
-
request: Request):
|
|
112
|
+
actor_type_name: str, actor_id: str, timer_name: str, request: Request
|
|
113
|
+
):
|
|
122
114
|
try:
|
|
123
115
|
# Read raw bytes from request stream
|
|
124
116
|
req_body = await request.body()
|
|
125
117
|
await ActorRuntime.fire_timer(actor_type_name, actor_id, timer_name, req_body)
|
|
126
118
|
except DaprInternalError as ex:
|
|
127
|
-
return _wrap_response(
|
|
128
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
129
|
-
ex.as_dict())
|
|
119
|
+
return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_json_safe_dict())
|
|
130
120
|
except Exception as ex:
|
|
131
121
|
return _wrap_response(
|
|
132
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
133
|
-
|
|
134
|
-
ERROR_CODE_UNKNOWN)
|
|
122
|
+
status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN
|
|
123
|
+
)
|
|
135
124
|
|
|
136
125
|
msg = f'called timer. actor: {actor_type_name}.{actor_id}, timer: {timer_name}'
|
|
137
126
|
logger.debug(msg)
|
|
138
127
|
return _wrap_response(status.HTTP_200_OK, msg)
|
|
139
128
|
|
|
140
|
-
@router.put(
|
|
141
|
-
|
|
129
|
+
@router.put(
|
|
130
|
+
'/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}',
|
|
131
|
+
tags=self._router_tags,
|
|
132
|
+
)
|
|
142
133
|
async def actor_reminder(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
reminder_name: str,
|
|
146
|
-
request: Request):
|
|
134
|
+
actor_type_name: str, actor_id: str, reminder_name: str, request: Request
|
|
135
|
+
):
|
|
147
136
|
try:
|
|
148
137
|
# Read raw bytes from request stream
|
|
149
138
|
req_body = await request.body()
|
|
150
|
-
await ActorRuntime.fire_reminder(
|
|
151
|
-
actor_type_name, actor_id, reminder_name, req_body)
|
|
139
|
+
await ActorRuntime.fire_reminder(actor_type_name, actor_id, reminder_name, req_body)
|
|
152
140
|
except DaprInternalError as ex:
|
|
153
|
-
return _wrap_response(
|
|
154
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
155
|
-
ex.as_dict())
|
|
141
|
+
return _wrap_response(status.HTTP_500_INTERNAL_SERVER_ERROR, ex.as_json_safe_dict())
|
|
156
142
|
except Exception as ex:
|
|
157
143
|
return _wrap_response(
|
|
158
|
-
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
159
|
-
|
|
160
|
-
ERROR_CODE_UNKNOWN)
|
|
144
|
+
status.HTTP_500_INTERNAL_SERVER_ERROR, repr(ex), ERROR_CODE_UNKNOWN
|
|
145
|
+
)
|
|
161
146
|
|
|
162
147
|
msg = f'called reminder. actor: {actor_type_name}.{actor_id}, reminder: {reminder_name}'
|
|
163
148
|
logger.debug(msg)
|
|
164
149
|
return _wrap_response(status.HTTP_200_OK, msg)
|
|
165
150
|
|
|
166
|
-
async def register_actor(self, actor: Type[Actor]) -> None:
|
|
167
|
-
await ActorRuntime.register_actor(actor)
|
|
151
|
+
async def register_actor(self, actor: Type[Actor], **kwargs) -> None:
|
|
152
|
+
await ActorRuntime.register_actor(actor, **kwargs)
|
|
168
153
|
logger.debug(f'registered actor: {actor.__class__.__name__}')
|
{dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/app.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""
|
|
3
|
-
Copyright
|
|
3
|
+
Copyright 2023 The Dapr Authors
|
|
4
4
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
you may not use this file except in compliance with the License.
|
|
6
6
|
You may obtain a copy of the License at
|
|
@@ -13,6 +13,7 @@ limitations under the License.
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
from typing import Dict, List, Optional
|
|
16
|
+
|
|
16
17
|
from fastapi import FastAPI # type: ignore
|
|
17
18
|
|
|
18
19
|
|
|
@@ -24,24 +25,24 @@ class DaprApp:
|
|
|
24
25
|
app_instance: The FastAPI instance to wrap.
|
|
25
26
|
"""
|
|
26
27
|
|
|
27
|
-
def __init__(self, app_instance: FastAPI,
|
|
28
|
-
router_tags: Optional[List[str]] = ['PubSub']):
|
|
28
|
+
def __init__(self, app_instance: FastAPI, router_tags: Optional[List[str]] = ['PubSub']):
|
|
29
29
|
# The router_tags should be added to all magic Dapr App PubSub methods implemented here
|
|
30
30
|
self._router_tags = router_tags
|
|
31
31
|
self._app = app_instance
|
|
32
32
|
self._subscriptions: List[Dict[str, object]] = []
|
|
33
33
|
|
|
34
|
-
self._app.add_api_route(
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
self._app.add_api_route(
|
|
35
|
+
'/dapr/subscribe', self._get_subscriptions, methods=['GET'], tags=self._router_tags
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def subscribe(
|
|
39
|
+
self,
|
|
40
|
+
pubsub: str,
|
|
41
|
+
topic: str,
|
|
42
|
+
metadata: Optional[Dict[str, str]] = {},
|
|
43
|
+
route: Optional[str] = None,
|
|
44
|
+
dead_letter_topic: Optional[str] = None,
|
|
45
|
+
):
|
|
45
46
|
"""
|
|
46
47
|
Subscribes to a topic on a pub/sub component.
|
|
47
48
|
|
|
@@ -73,21 +74,27 @@ class DaprApp:
|
|
|
73
74
|
Returns:
|
|
74
75
|
The decorator for the function.
|
|
75
76
|
"""
|
|
77
|
+
|
|
76
78
|
def decorator(func):
|
|
77
|
-
event_handler_route = f
|
|
78
|
-
|
|
79
|
-
self._app.add_api_route(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
event_handler_route = f'/events/{pubsub}/{topic}' if route is None else route
|
|
80
|
+
|
|
81
|
+
self._app.add_api_route(
|
|
82
|
+
event_handler_route, func, methods=['POST'], tags=self._router_tags
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self._subscriptions.append(
|
|
86
|
+
{
|
|
87
|
+
'pubsubname': pubsub,
|
|
88
|
+
'topic': topic,
|
|
89
|
+
'route': event_handler_route,
|
|
90
|
+
'metadata': metadata,
|
|
91
|
+
**(
|
|
92
|
+
{'deadLetterTopic': dead_letter_topic}
|
|
93
|
+
if dead_letter_topic is not None
|
|
94
|
+
else {}
|
|
95
|
+
),
|
|
96
|
+
}
|
|
97
|
+
)
|
|
91
98
|
|
|
92
99
|
return decorator
|
|
93
100
|
|
|
File without changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
Copyright
|
|
4
|
+
Copyright 2023 The Dapr Authors
|
|
5
5
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
you may not use this file except in compliance with the License.
|
|
7
7
|
You may obtain a copy of the License at
|
|
@@ -13,4 +13,4 @@ See the License for the specific language governing permissions and
|
|
|
13
13
|
limitations under the License.
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
|
-
__version__ =
|
|
16
|
+
__version__ = '1.16.0.dev'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version:
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: dapr-ext-fastapi-dev
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.16.0.dev59
|
|
4
4
|
Summary: The developmental release for Dapr FastAPI extension.
|
|
5
5
|
Home-page: https://dapr.io/
|
|
6
6
|
Author: Dapr Authors
|
|
@@ -8,15 +8,23 @@ Author-email: daprweb@microsoft.com
|
|
|
8
8
|
License: Apache
|
|
9
9
|
Project-URL: Documentation, https://github.com/dapr/docs
|
|
10
10
|
Project-URL: Source, https://github.com/dapr/python-sdk
|
|
11
|
-
Description: This is the developmental release for Dapr FastAPI extension.
|
|
12
|
-
Platform: UNKNOWN
|
|
13
11
|
Classifier: Development Status :: 5 - Production/Stable
|
|
14
12
|
Classifier: Intended Audience :: Developers
|
|
15
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
14
|
Classifier: Operating System :: OS Independent
|
|
17
15
|
Classifier: Programming Language :: Python
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
20
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
-
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: dapr>=1.16.0.dev
|
|
24
|
+
Requires-Dist: uvicorn>=0.11.6
|
|
25
|
+
Requires-Dist: fastapi>=0.60.1
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
This is the developmental release for Dapr FastAPI extension.
|
|
@@ -5,9 +5,12 @@ setup.py
|
|
|
5
5
|
dapr/ext/fastapi/__init__.py
|
|
6
6
|
dapr/ext/fastapi/actor.py
|
|
7
7
|
dapr/ext/fastapi/app.py
|
|
8
|
+
dapr/ext/fastapi/py.typed
|
|
8
9
|
dapr/ext/fastapi/version.py
|
|
9
10
|
dapr_ext_fastapi_dev.egg-info/PKG-INFO
|
|
10
11
|
dapr_ext_fastapi_dev.egg-info/SOURCES.txt
|
|
11
12
|
dapr_ext_fastapi_dev.egg-info/dependency_links.txt
|
|
12
13
|
dapr_ext_fastapi_dev.egg-info/requires.txt
|
|
13
|
-
dapr_ext_fastapi_dev.egg-info/top_level.txt
|
|
14
|
+
dapr_ext_fastapi_dev.egg-info/top_level.txt
|
|
15
|
+
tests/test_app.py
|
|
16
|
+
tests/test_dapractor.py
|
|
@@ -10,20 +10,21 @@ classifiers =
|
|
|
10
10
|
License :: OSI Approved :: Apache Software License
|
|
11
11
|
Operating System :: OS Independent
|
|
12
12
|
Programming Language :: Python
|
|
13
|
-
Programming Language :: Python :: 3.7
|
|
14
|
-
Programming Language :: Python :: 3.8
|
|
15
13
|
Programming Language :: Python :: 3.9
|
|
16
14
|
Programming Language :: Python :: 3.10
|
|
15
|
+
Programming Language :: Python :: 3.11
|
|
16
|
+
Programming Language :: Python :: 3.12
|
|
17
|
+
Programming Language :: Python :: 3.13
|
|
17
18
|
project_urls =
|
|
18
19
|
Documentation = https://github.com/dapr/docs
|
|
19
20
|
Source = https://github.com/dapr/python-sdk
|
|
20
21
|
|
|
21
22
|
[options]
|
|
22
|
-
python_requires = >=3.
|
|
23
|
+
python_requires = >=3.9
|
|
23
24
|
packages = find_namespace:
|
|
24
25
|
include_package_data = True
|
|
25
26
|
install_requires =
|
|
26
|
-
dapr
|
|
27
|
+
dapr >= 1.16.0.dev
|
|
27
28
|
uvicorn >= 0.11.6
|
|
28
29
|
fastapi >= 0.60.1
|
|
29
30
|
|
|
@@ -33,6 +34,10 @@ include =
|
|
|
33
34
|
exclude =
|
|
34
35
|
tests
|
|
35
36
|
|
|
37
|
+
[options.package_data]
|
|
38
|
+
dapr.ext.fastapi =
|
|
39
|
+
py.typed
|
|
40
|
+
|
|
36
41
|
[egg_info]
|
|
37
42
|
tag_build =
|
|
38
43
|
tag_date = 0
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
Copyright
|
|
4
|
+
Copyright 2023 The Dapr Authors
|
|
5
5
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
6
|
you may not use this file except in compliance with the License.
|
|
7
7
|
You may obtain a copy of the License at
|
|
@@ -31,7 +31,7 @@ def is_release():
|
|
|
31
31
|
name = 'dapr-ext-fastapi'
|
|
32
32
|
version = __version__
|
|
33
33
|
description = 'The official release of Dapr FastAPI extension.'
|
|
34
|
-
long_description =
|
|
34
|
+
long_description = """
|
|
35
35
|
This is the FastAPI extension for Dapr.
|
|
36
36
|
|
|
37
37
|
Dapr is a portable, serverless, event-driven runtime that makes it easy for developers to
|
|
@@ -42,7 +42,7 @@ Dapr codifies the best practices for building microservice applications into ope
|
|
|
42
42
|
independent, building blocks that enable you to build portable applications with the language
|
|
43
43
|
and framework of your choice. Each building block is independent and you can use one, some,
|
|
44
44
|
or all of them in your application.
|
|
45
|
-
|
|
45
|
+
""".lstrip()
|
|
46
46
|
|
|
47
47
|
# Get build number from GITHUB_RUN_NUMBER environment variable
|
|
48
48
|
build_number = os.environ.get('GITHUB_RUN_NUMBER', '0')
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
|
|
3
|
+
from dapr.ext.fastapi import DaprApp
|
|
4
|
+
from fastapi import FastAPI
|
|
5
|
+
from fastapi.testclient import TestClient
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Message(BaseModel):
|
|
10
|
+
body: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DaprAppTest(unittest.TestCase):
|
|
14
|
+
def setUp(self):
|
|
15
|
+
self.app = FastAPI()
|
|
16
|
+
self.dapr_app = DaprApp(self.app)
|
|
17
|
+
self.client = TestClient(self.app)
|
|
18
|
+
|
|
19
|
+
def test_subscribe_subscription_registered(self):
|
|
20
|
+
@self.dapr_app.subscribe(pubsub='pubsub', topic='test')
|
|
21
|
+
def event_handler(event_data: Message):
|
|
22
|
+
return 'default route'
|
|
23
|
+
|
|
24
|
+
self.assertEqual(len(self.dapr_app._subscriptions), 1)
|
|
25
|
+
|
|
26
|
+
self.assertIn('/dapr/subscribe', [route.path for route in self.app.router.routes])
|
|
27
|
+
self.assertIn('/events/pubsub/test', [route.path for route in self.app.router.routes])
|
|
28
|
+
|
|
29
|
+
response = self.client.get('/dapr/subscribe')
|
|
30
|
+
self.assertEqual(
|
|
31
|
+
[
|
|
32
|
+
{
|
|
33
|
+
'pubsubname': 'pubsub',
|
|
34
|
+
'topic': 'test',
|
|
35
|
+
'route': '/events/pubsub/test',
|
|
36
|
+
'metadata': {},
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
response.json(),
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
response = self.client.post('/events/pubsub/test', json={'body': 'new message'})
|
|
43
|
+
self.assertEqual(response.status_code, 200)
|
|
44
|
+
self.assertEqual(response.text, '"default route"')
|
|
45
|
+
|
|
46
|
+
def test_subscribe_with_route_subscription_registered_with_custom_route(self):
|
|
47
|
+
@self.dapr_app.subscribe(pubsub='pubsub', topic='test', route='/do-something')
|
|
48
|
+
def event_handler(event_data: Message):
|
|
49
|
+
return 'custom route'
|
|
50
|
+
|
|
51
|
+
self.assertEqual(len(self.dapr_app._subscriptions), 1)
|
|
52
|
+
|
|
53
|
+
self.assertIn('/dapr/subscribe', [route.path for route in self.app.router.routes])
|
|
54
|
+
self.assertIn('/do-something', [route.path for route in self.app.router.routes])
|
|
55
|
+
|
|
56
|
+
response = self.client.get('/dapr/subscribe')
|
|
57
|
+
self.assertEqual(
|
|
58
|
+
[{'pubsubname': 'pubsub', 'topic': 'test', 'route': '/do-something', 'metadata': {}}],
|
|
59
|
+
response.json(),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
response = self.client.post('/do-something', json={'body': 'new message'})
|
|
63
|
+
self.assertEqual(response.status_code, 200)
|
|
64
|
+
self.assertEqual(response.text, '"custom route"')
|
|
65
|
+
|
|
66
|
+
def test_subscribe_metadata(self):
|
|
67
|
+
handler_metadata = {'rawPayload': 'true'}
|
|
68
|
+
|
|
69
|
+
@self.dapr_app.subscribe(pubsub='pubsub', topic='test', metadata=handler_metadata)
|
|
70
|
+
def event_handler(event_data: Message):
|
|
71
|
+
return 'custom metadata'
|
|
72
|
+
|
|
73
|
+
self.assertEqual((self.dapr_app._subscriptions[0]['metadata']['rawPayload']), 'true')
|
|
74
|
+
|
|
75
|
+
response = self.client.get('/dapr/subscribe')
|
|
76
|
+
self.assertEqual(
|
|
77
|
+
[
|
|
78
|
+
{
|
|
79
|
+
'pubsubname': 'pubsub',
|
|
80
|
+
'topic': 'test',
|
|
81
|
+
'route': '/events/pubsub/test',
|
|
82
|
+
'metadata': {'rawPayload': 'true'},
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
response.json(),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
response = self.client.post('/events/pubsub/test', json={'body': 'new message'})
|
|
89
|
+
self.assertEqual(response.status_code, 200)
|
|
90
|
+
self.assertEqual(response.text, '"custom metadata"')
|
|
91
|
+
|
|
92
|
+
def test_router_tag(self):
|
|
93
|
+
app1 = FastAPI()
|
|
94
|
+
app2 = FastAPI()
|
|
95
|
+
app3 = FastAPI()
|
|
96
|
+
DaprApp(app_instance=app1, router_tags=['MyTag', 'PubSub']).subscribe(
|
|
97
|
+
pubsub='mypubsub', topic='test'
|
|
98
|
+
)
|
|
99
|
+
DaprApp(app_instance=app2).subscribe(pubsub='mypubsub', topic='test')
|
|
100
|
+
DaprApp(app_instance=app3, router_tags=None).subscribe(pubsub='mypubsub', topic='test')
|
|
101
|
+
|
|
102
|
+
PATHS_WITH_EXPECTED_TAGS = ['/dapr/subscribe', '/events/mypubsub/test']
|
|
103
|
+
|
|
104
|
+
foundTags = False
|
|
105
|
+
for route in app1.router.routes:
|
|
106
|
+
if hasattr(route, 'tags'):
|
|
107
|
+
self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS)
|
|
108
|
+
self.assertEqual(['MyTag', 'PubSub'], route.tags)
|
|
109
|
+
foundTags = True
|
|
110
|
+
if not foundTags:
|
|
111
|
+
self.fail('No tags found')
|
|
112
|
+
|
|
113
|
+
foundTags = False
|
|
114
|
+
for route in app2.router.routes:
|
|
115
|
+
if hasattr(route, 'tags'):
|
|
116
|
+
self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS)
|
|
117
|
+
self.assertEqual(['PubSub'], route.tags)
|
|
118
|
+
foundTags = True
|
|
119
|
+
if not foundTags:
|
|
120
|
+
self.fail('No tags found')
|
|
121
|
+
|
|
122
|
+
for route in app3.router.routes:
|
|
123
|
+
if hasattr(route, 'tags'):
|
|
124
|
+
if len(route.tags) > 0:
|
|
125
|
+
self.fail('Found tags on route that should not have any')
|
|
126
|
+
|
|
127
|
+
def test_subscribe_dead_letter(self):
|
|
128
|
+
dead_letter_topic = 'dead-test'
|
|
129
|
+
|
|
130
|
+
@self.dapr_app.subscribe(pubsub='pubsub', topic='test', dead_letter_topic=dead_letter_topic)
|
|
131
|
+
def event_handler(event_data: Message):
|
|
132
|
+
return 'dead letter test'
|
|
133
|
+
|
|
134
|
+
self.assertEqual((self.dapr_app._subscriptions[0]['deadLetterTopic']), dead_letter_topic)
|
|
135
|
+
|
|
136
|
+
response = self.client.get('/dapr/subscribe')
|
|
137
|
+
self.assertEqual(
|
|
138
|
+
[
|
|
139
|
+
{
|
|
140
|
+
'pubsubname': 'pubsub',
|
|
141
|
+
'topic': 'test',
|
|
142
|
+
'route': '/events/pubsub/test',
|
|
143
|
+
'metadata': {},
|
|
144
|
+
'deadLetterTopic': dead_letter_topic,
|
|
145
|
+
}
|
|
146
|
+
],
|
|
147
|
+
response.json(),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
response = self.client.post('/events/pubsub/test', json={'body': 'new message'})
|
|
151
|
+
self.assertEqual(response.status_code, 200)
|
|
152
|
+
self.assertEqual(response.text, '"dead letter test"')
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
if __name__ == '__main__':
|
|
156
|
+
unittest.main()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Copyright 2023 The Dapr Authors
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import unittest
|
|
18
|
+
|
|
19
|
+
from dapr.ext.fastapi.actor import DaprActor, _wrap_response
|
|
20
|
+
from fastapi import FastAPI
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class DaprActorTest(unittest.TestCase):
|
|
24
|
+
def test_wrap_response_str(self):
|
|
25
|
+
r = _wrap_response(200, 'fake_message')
|
|
26
|
+
self.assertEqual({'message': 'fake_message'}, json.loads(r.body))
|
|
27
|
+
self.assertEqual(200, r.status_code)
|
|
28
|
+
|
|
29
|
+
def test_wrap_response_str_err(self):
|
|
30
|
+
r = _wrap_response(400, 'fake_message', 'ERR_FAKE')
|
|
31
|
+
self.assertEqual({'message': 'fake_message', 'errorCode': 'ERR_FAKE'}, json.loads(r.body))
|
|
32
|
+
self.assertEqual(400, r.status_code)
|
|
33
|
+
|
|
34
|
+
def test_wrap_response_bytes_text(self):
|
|
35
|
+
r = _wrap_response(200, b'fake_bytes_message', content_type='text/plain')
|
|
36
|
+
self.assertEqual(b'fake_bytes_message', r.body)
|
|
37
|
+
self.assertEqual(200, r.status_code)
|
|
38
|
+
self.assertEqual('text/plain', r.media_type)
|
|
39
|
+
|
|
40
|
+
def test_wrap_response_obj(self):
|
|
41
|
+
fake_data = {'message': 'ok'}
|
|
42
|
+
r = _wrap_response(200, fake_data)
|
|
43
|
+
self.assertEqual(fake_data, json.loads(r.body))
|
|
44
|
+
self.assertEqual(200, r.status_code)
|
|
45
|
+
|
|
46
|
+
def test_router_tag(self):
|
|
47
|
+
app1 = FastAPI()
|
|
48
|
+
app2 = FastAPI()
|
|
49
|
+
app3 = FastAPI()
|
|
50
|
+
DaprActor(app=app1, router_tags=['MyTag', 'Actor'])
|
|
51
|
+
DaprActor(app=app2)
|
|
52
|
+
DaprActor(app=app3, router_tags=None)
|
|
53
|
+
|
|
54
|
+
PATHS_WITH_EXPECTED_TAGS = [
|
|
55
|
+
'/healthz',
|
|
56
|
+
'/dapr/config',
|
|
57
|
+
'/actors/{actor_type_name}/{actor_id}',
|
|
58
|
+
'/actors/{actor_type_name}/{actor_id}/method/{method_name}',
|
|
59
|
+
'/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}',
|
|
60
|
+
'/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}',
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
foundTags = False
|
|
64
|
+
for route in app1.router.routes:
|
|
65
|
+
if hasattr(route, 'tags'):
|
|
66
|
+
self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS)
|
|
67
|
+
self.assertEqual(['MyTag', 'Actor'], route.tags)
|
|
68
|
+
foundTags = True
|
|
69
|
+
if not foundTags:
|
|
70
|
+
self.fail('No tags found')
|
|
71
|
+
|
|
72
|
+
foundTags = False
|
|
73
|
+
for route in app2.router.routes:
|
|
74
|
+
if hasattr(route, 'tags'):
|
|
75
|
+
self.assertIn(route.path, PATHS_WITH_EXPECTED_TAGS)
|
|
76
|
+
self.assertEqual(['Actor'], route.tags)
|
|
77
|
+
foundTags = True
|
|
78
|
+
if not foundTags:
|
|
79
|
+
self.fail('No tags found')
|
|
80
|
+
|
|
81
|
+
for route in app3.router.routes:
|
|
82
|
+
if hasattr(route, 'tags'):
|
|
83
|
+
if len(route.tags) > 0:
|
|
84
|
+
self.fail('Found tags on route that should not have any')
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == '__main__':
|
|
88
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|