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.
Files changed (17) hide show
  1. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/PKG-INFO +15 -7
  2. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/__init__.py +2 -6
  3. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/actor.py +47 -62
  4. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/app.py +35 -28
  5. dapr_ext_fastapi_dev-1.16.0.dev59/dapr/ext/fastapi/py.typed +0 -0
  6. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/dapr/ext/fastapi/version.py +2 -2
  7. {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
  8. {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
  9. {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
  10. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/setup.cfg +9 -4
  11. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/setup.py +3 -3
  12. dapr_ext_fastapi_dev-1.16.0.dev59/tests/test_app.py +156 -0
  13. dapr_ext_fastapi_dev-1.16.0.dev59/tests/test_dapractor.py +88 -0
  14. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/LICENSE +0 -0
  15. {dapr-ext-fastapi-dev-1.9.0rc1.dev1402 → dapr_ext_fastapi_dev-1.16.0.dev59}/README.rst +0 -0
  16. {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
  17. {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.2
1
+ Metadata-Version: 2.4
2
2
  Name: dapr-ext-fastapi-dev
3
- Version: 1.9.0rc1.dev1402
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
- Requires-Python: >=3.7
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 2021 The Dapr Authors
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 2021 The Dapr Authors
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, List
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 DaprInternalError, ERROR_CODE_UNKNOWN
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 = "application/json; utf-8"
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
- status_code: int,
32
- msg: Any,
33
- error_code: Optional[str] = None,
34
- content_type: Optional[str] = DEFAULT_CONTENT_TYPE):
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("/healthz", tags=self._router_tags)
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
- repr(ex),
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('/actors/{actor_type_name}/{actor_id}/method/{method_name}',
90
- tags=self._router_tags)
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
- actor_type_name: str,
93
- actor_id: str,
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
- repr(ex),
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('/actors/{actor_type_name}/{actor_id}/method/timer/{timer_name}',
116
- tags=self._router_tags)
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
- actor_type_name: str,
119
- actor_id: str,
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
- repr(ex),
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('/actors/{actor_type_name}/{actor_id}/method/remind/{reminder_name}',
141
- tags=self._router_tags)
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
- actor_type_name: str,
144
- actor_id: str,
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
- repr(ex),
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__}')
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """
3
- Copyright 2021 The Dapr Authors
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("/dapr/subscribe",
35
- self._get_subscriptions,
36
- methods=["GET"],
37
- tags=self._router_tags)
38
-
39
- def subscribe(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):
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"/events/{pubsub}/{topic}" if route is None else route
78
-
79
- self._app.add_api_route(event_handler_route,
80
- func,
81
- methods=["POST"],
82
- tags=self._router_tags)
83
-
84
- self._subscriptions.append({
85
- "pubsubname": pubsub,
86
- "topic": topic,
87
- "route": event_handler_route,
88
- "metadata": metadata,
89
- **({"deadLetterTopic": dead_letter_topic} if dead_letter_topic is not None else {})
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
 
@@ -1,7 +1,7 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  """
4
- Copyright 2021 The Dapr Authors
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__ = "1.9.0rc1.dev"
16
+ __version__ = '1.16.0.dev'
@@ -1,6 +1,6 @@
1
- Metadata-Version: 1.2
1
+ Metadata-Version: 2.4
2
2
  Name: dapr-ext-fastapi-dev
3
- Version: 1.9.0rc1.dev1402
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
- Requires-Python: >=3.7
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
@@ -1,3 +1,3 @@
1
- dapr-dev>=1.9.0rc1.dev
1
+ dapr>=1.16.0.dev
2
2
  uvicorn>=0.11.6
3
3
  fastapi>=0.60.1
@@ -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.7
23
+ python_requires = >=3.9
23
24
  packages = find_namespace:
24
25
  include_package_data = True
25
26
  install_requires =
26
- dapr-dev >= 1.9.0rc1.dev
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 2021 The Dapr Authors
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
- '''.lstrip()
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()