opal-server 0.7.0__tar.gz → 0.7.2__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 (43) hide show
  1. {opal-server-0.7.0 → opal-server-0.7.2}/PKG-INFO +10 -6
  2. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/config.py +21 -3
  3. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/data/api.py +1 -1
  4. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/data/data_update_publisher.py +5 -3
  5. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/watcher/callbacks.py +4 -1
  6. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/watcher/factory.py +12 -0
  7. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/watcher/task.py +9 -3
  8. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/scopes/api.py +3 -4
  9. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/scopes/service.py +1 -2
  10. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server.egg-info/PKG-INFO +10 -6
  11. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server.egg-info/requires.txt +2 -2
  12. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/__init__.py +0 -0
  13. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/cli.py +0 -0
  14. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/data/__init__.py +0 -0
  15. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/git_fetcher.py +0 -0
  16. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/loadlimiting.py +0 -0
  17. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/main.py +0 -0
  18. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/__init__.py +0 -0
  19. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/bundles/__init__.py +0 -0
  20. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/bundles/api.py +0 -0
  21. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/watcher/__init__.py +0 -0
  22. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/webhook/__init__.py +0 -0
  23. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/webhook/api.py +0 -0
  24. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/webhook/deps.py +0 -0
  25. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/policy/webhook/listener.py +0 -0
  26. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/publisher.py +0 -0
  27. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/pubsub.py +0 -0
  28. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/redis.py +0 -0
  29. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/scopes/__init__.py +0 -0
  30. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/scopes/loader.py +0 -0
  31. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/scopes/scope_repository.py +0 -0
  32. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/scopes/task.py +0 -0
  33. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/security/__init__.py +0 -0
  34. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/security/api.py +0 -0
  35. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/security/jwks.py +0 -0
  36. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/server.py +0 -0
  37. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server/statistics.py +0 -0
  38. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server.egg-info/SOURCES.txt +0 -0
  39. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server.egg-info/dependency_links.txt +0 -0
  40. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server.egg-info/entry_points.txt +0 -0
  41. {opal-server-0.7.0 → opal-server-0.7.2}/opal_server.egg-info/top_level.txt +0 -0
  42. {opal-server-0.7.0 → opal-server-0.7.2}/setup.cfg +0 -0
  43. {opal-server-0.7.0 → opal-server-0.7.2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opal-server
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: OPAL is an administration layer for Open Policy Agent (OPA), detecting changes to both policy and data and pushing live updates to your agents. The opal-server creates a pub/sub channel clients can subscribe to (i.e: acts as coordinator). The server also tracks a git repository (via webhook) for updates to policy (or static data) and accepts continuous data update notifications via REST api, which are then pushed to clients.
5
5
  Home-page: https://github.com/permitio/opal
6
6
  Author: Or Weis, Asaf Cohen
@@ -52,26 +52,29 @@ Open Policy Administration Layer
52
52
  <a href="https://twitter.com/intent/follow?original_referer=https%3A%2F%2Fpublish.twitter.com%2F%3FbuttonType%3DFollowButton%26query%3Dhttps%253A%252F%252Ftwitter.com%252Fpermit_io%26widget%3DButton&ref_src=twsrc%5Etfw&region=follow_link&screen_name=permit_io&tw_p=followbutton"><img src="https://img.shields.io/twitter/follow/permit_io?label=Follow%20%40permit_io&style=social">
53
53
  </a>
54
54
 
55
-
56
-
57
55
  ## What is OPAL?
58
56
 
59
- OPAL is an administration layer for <a href="https://www.openpolicyagent.org/">Open Policy Agent (OPA)</a>, detecting changes to both policy and policy data in realtime and pushing live updates to your agents. OPAL brings open-policy up to the speed needed by live applications.
57
+ OPAL is an administration layer for Policy Engines such as <a target="_blank" href="https://www.openpolicyagent.org/">Open Policy Agent (OPA)</a>, and <a target="_blank" href="https://github.com/permitio/cedar-agent">AWS' Cedar Agent</a> detecting changes to both policy and policy data in realtime and pushing live updates to your agents. OPAL brings open-policy up to the speed needed by live applications.
60
58
 
61
59
  As your application state changes (whether it's via your APIs, DBs, git, S3 or 3rd-party SaaS services), OPAL will make sure your services are always in sync with the authorization data and policy they need (and only those they need).
62
60
 
63
- Check out our main site at <a href="https://opal.ac">OPAL.ac</a>, <a href="https://youtu.be/tG8jrdcc7Zo">this video</a> briefly explaining OPAL and how it works with OPA, and a deeper dive into it at [this OWASP DevSlop talk](https://www.youtube.com/watch?v=1_Iz0tRQCH4).
61
+ Check out our main site at <a target="_blank" href="https://opal.ac">OPAL.ac</a>, <a target="_blank" href="https://youtu.be/tG8jrdcc7Zo">this video</a> briefly explaining OPAL and how it works with OPA, and a deeper dive into it at [this OWASP DevSlop talk](https://www.youtube.com/watch?v=1_Iz0tRQCH4).
64
62
 
65
63
  ## Why use OPAL?
66
64
 
67
65
  OPAL is the easiest way to keep your solution's authorization layer up-to-date in realtime. It aggregates policy and data from across the field and integrates them seamlessly into the authorization layer, and is microservices and cloud-native.
68
66
 
69
- ## OPA + OPAL = 💜
67
+ ## OPA + OPAL == 💜
70
68
 
71
69
  While OPA (Open Policy Agent) decouples policy from code in a highly-performant and elegant way, the challenge of keeping policy agents up-to-date remains.
72
70
  This is especially true in applications, where each user interaction or API call may affect access-control decisions.
73
71
  OPAL runs in the background, supercharging policy-agents, keeping them in sync with events in realtime.
74
72
 
73
+ ## AWS Cedar + OPAL == 💪
74
+
75
+ Cedar is a very powerful policy language, which powers AWS' AVP (Amazon Verified Permissions) - but what if you want to enjoy the power of Cedar on another cloud, locally, or on premise?
76
+ This is where [Cedar-Agent](https://github.com/permitio/cedar-agent) and OPAL come in.
77
+
75
78
  ## Documentation
76
79
 
77
80
  - 📃 &nbsp; [Full documentation is available here](https://docs.opal.ac)
@@ -156,3 +159,4 @@ If you are using our project, please consider giving us a ⭐️
156
159
  ## There's more!
157
160
 
158
161
  - Check out [OPToggles](https://github.com/permitio/OPToggles), which enables you to create user targeted feature flags/toggles based on Open Policy managed authorization rules!
162
+ - Check out [Cedar-Agent](https://github.com/permitio/cedar-agent), the easiest way to deploy & run AWS Cedar.
@@ -15,6 +15,11 @@ class PolicySourceTypes(str, Enum):
15
15
  Api = "API"
16
16
 
17
17
 
18
+ class PolicyBundleServerType(str, Enum):
19
+ HTTP = "HTTP"
20
+ AWS_S3 = "AWS-S3"
21
+
22
+
18
23
  class ServerRole(str, Enum):
19
24
  Primary = "primary"
20
25
  Secondary = "secondary"
@@ -107,10 +112,21 @@ class OpalServerConfig(Confi):
107
112
  LEADER_LOCK_FILE_PATH = confi.str(
108
113
  "LEADER_LOCK_FILE_PATH", "/tmp/opal_server_leader.lock"
109
114
  )
115
+ POLICY_BUNDLE_SERVER_TYPE = confi.enum(
116
+ "POLICY_BUNDLE_SERVER_TYPE",
117
+ PolicyBundleServerType,
118
+ PolicyBundleServerType.HTTP,
119
+ description="The type of bundle server e.g. basic HTTP , AWS S3. (affects how we authenticate with it)",
120
+ )
110
121
  POLICY_BUNDLE_SERVER_TOKEN = confi.str(
111
122
  "POLICY_BUNDLE_SERVER_TOKEN",
112
123
  None,
113
- description="Bearer token to sent to API bundle server",
124
+ description="Secret token to be sent to API bundle server",
125
+ )
126
+ POLICY_BUNDLE_SERVER_TOKEN_ID = confi.str(
127
+ "POLICY_BUNDLE_SERVER_TOKEN_ID",
128
+ None,
129
+ description="The id of the secret token to be sent to API bundle server",
114
130
  )
115
131
  POLICY_BUNDLE_TMP_PATH = confi.str(
116
132
  "POLICY_BUNDLE_TMP_PATH",
@@ -159,7 +175,9 @@ class OpalServerConfig(Confi):
159
175
 
160
176
  # Data updates
161
177
  ALL_DATA_TOPIC = confi.str(
162
- "ALL_DATA_TOPIC", DEFAULT_DATA_TOPIC, description="Top level topic for data"
178
+ "ALL_DATA_TOPIC",
179
+ DEFAULT_DATA_TOPIC,
180
+ description="Top level topic for data",
163
181
  )
164
182
  ALL_DATA_ROUTE = confi.str("ALL_DATA_ROUTE", "/policy-data")
165
183
  ALL_DATA_URL = confi.str(
@@ -243,7 +261,7 @@ class OpalServerConfig(Confi):
243
261
  SERVER_PORT = confi.str(
244
262
  "SERVER_PORT",
245
263
  None,
246
- # Users have expirienced errors when kubernetes sets the envar OPAL_SERVER_PORT="tcp://..." (which fails to parse as a port integer).
264
+ # Users have experienced errors when kubernetes sets the env-var OPAL_SERVER_PORT="tcp://..." (which fails to parse as a port integer).
247
265
  description="Deprecated, use SERVER_BIND_PORT instead",
248
266
  )
249
267
 
@@ -128,7 +128,7 @@ def init_data_updates_router(
128
128
  logger.error(f"Unauthorized to publish update: {repr(e)}")
129
129
  raise
130
130
 
131
- data_update_publisher.publish_data_updates(update)
131
+ await data_update_publisher.publish_data_updates(update)
132
132
  return {"status": "ok"}
133
133
 
134
134
  return router
@@ -63,7 +63,7 @@ class DataUpdatePublisher:
63
63
 
64
64
  return topic_combos
65
65
 
66
- def publish_data_updates(self, update: DataUpdate):
66
+ async def publish_data_updates(self, update: DataUpdate):
67
67
  """Notify OPAL subscribers of a new data update by topic.
68
68
 
69
69
  Args:
@@ -107,7 +107,9 @@ class DataUpdatePublisher:
107
107
  reason=update.reason,
108
108
  entries=logged_entries,
109
109
  )
110
- self._publisher.publish(list(all_topic_combos), update)
110
+
111
+ async with self._publisher:
112
+ await self._publisher.publish(list(all_topic_combos), update)
111
113
 
112
114
  async def _periodic_update_callback(
113
115
  self, update: DataSourceEntryWithPollingInterval
@@ -118,7 +120,7 @@ class DataUpdatePublisher:
118
120
  )
119
121
  # Create new publish entry
120
122
 
121
- return self.publish_data_updates(
123
+ return await self.publish_data_updates(
122
124
  DataUpdate(reason="Periodic Update", entries=[update])
123
125
  )
124
126
 
@@ -116,4 +116,7 @@ async def publish_changed_directories(
116
116
  )
117
117
 
118
118
  if notification:
119
- publisher.publish(topics=notification.topics, data=notification.update.dict())
119
+ async with publisher:
120
+ await publisher.publish(
121
+ topics=notification.topics, data=notification.update.dict()
122
+ )
@@ -25,6 +25,8 @@ def setup_watcher_task(
25
25
  polling_interval: int = None,
26
26
  request_timeout: int = None,
27
27
  policy_bundle_token: str = None,
28
+ policy_bundle_token_id: str = None,
29
+ policy_bundle_server_type: str = None,
28
30
  extensions: Optional[List[str]] = None,
29
31
  bundle_ignore: Optional[List[str]] = None,
30
32
  ) -> BasePolicyWatcherTask:
@@ -41,6 +43,8 @@ def setup_watcher_task(
41
43
  polling_interval(int): how many seconds need to wait between polling
42
44
  request_timeout(int): how many seconds need to wait until timeout
43
45
  policy_bundle_token(int): auth token to include in connections to OPAL server. Defaults to POLICY_BUNDLE_SERVER_TOKEN.
46
+ policy_bundle_token_id(int): id token to include in connections to OPAL server. Defaults to POLICY_BUNDLE_SERVER_TOKEN_ID.
47
+ policy_bundle_server_type (str): type of policy bundle server (HTTP S3). Defaults to POLICY_BUNDLE_SERVER_TYPE
44
48
  extensions(list(str), optional): list of extantions to check when new policy arrive default is FILTER_FILE_EXTENSIONS
45
49
  bundle_ignore(list(str), optional): list of glob paths to use for excluding files from bundle default is OPA_BUNDLE_IGNORE
46
50
  """
@@ -105,11 +109,19 @@ def setup_watcher_task(
105
109
  logger.warning(
106
110
  "POLICY_BUNDLE_URL is unset but policy watcher is enabled! disabling watcher."
107
111
  )
112
+ policy_bundle_token_id = load_conf_if_none(
113
+ policy_bundle_token_id, opal_server_config.POLICY_BUNDLE_SERVER_TOKEN_ID
114
+ )
115
+ policy_bundle_server_type = load_conf_if_none(
116
+ policy_bundle_server_type, opal_server_config.POLICY_BUNDLE_SERVER_TYPE
117
+ )
108
118
  watcher = ApiPolicySource(
109
119
  remote_source_url=remote_source_url,
110
120
  local_clone_path=clone_path,
111
121
  polling_interval=polling_interval,
112
122
  token=policy_bundle_token,
123
+ token_id=policy_bundle_token_id,
124
+ bundle_server_type=policy_bundle_server_type,
113
125
  policy_bundle_path=opal_server_config.POLICY_BUNDLE_TMP_PATH,
114
126
  policy_bundle_git_add_pattern=opal_server_config.POLICY_BUNDLE_GIT_ADD_PATTERN,
115
127
  )
@@ -17,6 +17,7 @@ class BasePolicyWatcherTask:
17
17
  self._tasks: List[asyncio.Task] = []
18
18
  self._should_stop: Optional[asyncio.Event] = None
19
19
  self._pubsub_endpoint = pubsub_endpoint
20
+ self._webhook_tasks: List[asyncio.Task] = []
20
21
 
21
22
  async def __aenter__(self):
22
23
  await self.start()
@@ -26,8 +27,13 @@ class BasePolicyWatcherTask:
26
27
  await self.stop()
27
28
 
28
29
  async def _on_webhook(self, topic: Topic, data: Any):
29
- logger.info("Webhook listener triggered")
30
- self._tasks.append(asyncio.create_task(self.trigger(topic, data)))
30
+ logger.info(f"Webhook listener triggered ({len(self._webhook_tasks)})")
31
+ for task in self._webhook_tasks:
32
+ if task.done():
33
+ # Clean references to finished tasks
34
+ self._webhook_tasks.remove(task)
35
+
36
+ self._webhook_tasks.append(asyncio.create_task(self.trigger(topic, data)))
31
37
 
32
38
  async def _listen_to_webhook_notifications(self):
33
39
  # Webhook api route can be hit randomly in all workers, so it publishes a message to the webhook topic.
@@ -58,7 +64,7 @@ class BasePolicyWatcherTask:
58
64
  async def stop(self):
59
65
  """stops all policy watcher tasks."""
60
66
  logger.info("Stopping policy watcher")
61
- for task in self._tasks:
67
+ for task in self._tasks + self._webhook_tasks:
62
68
  if not task.done():
63
69
  task.cancel()
64
70
  await asyncio.gather(*self._tasks, return_exceptions=True)
@@ -342,10 +342,9 @@ def init_scope_router(
342
342
  for entry in update.entries:
343
343
  entry.topics = [f"data:{topic}" for topic in entry.topics]
344
344
 
345
- async with ScopedServerSideTopicPublisher(
346
- pubsub_endpoint, scope_id
347
- ) as publisher:
348
- DataUpdatePublisher(publisher).publish_data_updates(update)
345
+ await DataUpdatePublisher(
346
+ ScopedServerSideTopicPublisher(pubsub_endpoint, scope_id)
347
+ ).publish_data_updates(update)
349
348
  except Unauthorized as ex:
350
349
  logger.error(f"Unauthorized to publish update: {repr(ex)}")
351
350
  raise
@@ -94,8 +94,7 @@ class NewCommitsCallbacks(PolicyFetcherCallbacks):
94
94
  async with ScopedServerSideTopicPublisher(
95
95
  self._pubsub_endpoint, self._scope_id
96
96
  ) as publisher:
97
- publisher.publish(notification.topics, notification.update)
98
- await publisher.wait()
97
+ await publisher.publish(notification.topics, notification.update)
99
98
 
100
99
 
101
100
  class ScopesService:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: opal-server
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: OPAL is an administration layer for Open Policy Agent (OPA), detecting changes to both policy and data and pushing live updates to your agents. The opal-server creates a pub/sub channel clients can subscribe to (i.e: acts as coordinator). The server also tracks a git repository (via webhook) for updates to policy (or static data) and accepts continuous data update notifications via REST api, which are then pushed to clients.
5
5
  Home-page: https://github.com/permitio/opal
6
6
  Author: Or Weis, Asaf Cohen
@@ -52,26 +52,29 @@ Open Policy Administration Layer
52
52
  <a href="https://twitter.com/intent/follow?original_referer=https%3A%2F%2Fpublish.twitter.com%2F%3FbuttonType%3DFollowButton%26query%3Dhttps%253A%252F%252Ftwitter.com%252Fpermit_io%26widget%3DButton&ref_src=twsrc%5Etfw&region=follow_link&screen_name=permit_io&tw_p=followbutton"><img src="https://img.shields.io/twitter/follow/permit_io?label=Follow%20%40permit_io&style=social">
53
53
  </a>
54
54
 
55
-
56
-
57
55
  ## What is OPAL?
58
56
 
59
- OPAL is an administration layer for <a href="https://www.openpolicyagent.org/">Open Policy Agent (OPA)</a>, detecting changes to both policy and policy data in realtime and pushing live updates to your agents. OPAL brings open-policy up to the speed needed by live applications.
57
+ OPAL is an administration layer for Policy Engines such as <a target="_blank" href="https://www.openpolicyagent.org/">Open Policy Agent (OPA)</a>, and <a target="_blank" href="https://github.com/permitio/cedar-agent">AWS' Cedar Agent</a> detecting changes to both policy and policy data in realtime and pushing live updates to your agents. OPAL brings open-policy up to the speed needed by live applications.
60
58
 
61
59
  As your application state changes (whether it's via your APIs, DBs, git, S3 or 3rd-party SaaS services), OPAL will make sure your services are always in sync with the authorization data and policy they need (and only those they need).
62
60
 
63
- Check out our main site at <a href="https://opal.ac">OPAL.ac</a>, <a href="https://youtu.be/tG8jrdcc7Zo">this video</a> briefly explaining OPAL and how it works with OPA, and a deeper dive into it at [this OWASP DevSlop talk](https://www.youtube.com/watch?v=1_Iz0tRQCH4).
61
+ Check out our main site at <a target="_blank" href="https://opal.ac">OPAL.ac</a>, <a target="_blank" href="https://youtu.be/tG8jrdcc7Zo">this video</a> briefly explaining OPAL and how it works with OPA, and a deeper dive into it at [this OWASP DevSlop talk](https://www.youtube.com/watch?v=1_Iz0tRQCH4).
64
62
 
65
63
  ## Why use OPAL?
66
64
 
67
65
  OPAL is the easiest way to keep your solution's authorization layer up-to-date in realtime. It aggregates policy and data from across the field and integrates them seamlessly into the authorization layer, and is microservices and cloud-native.
68
66
 
69
- ## OPA + OPAL = 💜
67
+ ## OPA + OPAL == 💜
70
68
 
71
69
  While OPA (Open Policy Agent) decouples policy from code in a highly-performant and elegant way, the challenge of keeping policy agents up-to-date remains.
72
70
  This is especially true in applications, where each user interaction or API call may affect access-control decisions.
73
71
  OPAL runs in the background, supercharging policy-agents, keeping them in sync with events in realtime.
74
72
 
73
+ ## AWS Cedar + OPAL == 💪
74
+
75
+ Cedar is a very powerful policy language, which powers AWS' AVP (Amazon Verified Permissions) - but what if you want to enjoy the power of Cedar on another cloud, locally, or on premise?
76
+ This is where [Cedar-Agent](https://github.com/permitio/cedar-agent) and OPAL come in.
77
+
75
78
  ## Documentation
76
79
 
77
80
  - 📃 &nbsp; [Full documentation is available here](https://docs.opal.ac)
@@ -156,3 +159,4 @@ If you are using our project, please consider giving us a ⭐️
156
159
  ## There's more!
157
160
 
158
161
  - Check out [OPToggles](https://github.com/permitio/OPToggles), which enables you to create user targeted feature flags/toggles based on Open Policy managed authorization rules!
162
+ - Check out [Cedar-Agent](https://github.com/permitio/cedar-agent), the easiest way to deploy & run AWS Cedar.
@@ -9,12 +9,12 @@ aioredis<3,>=2.0.1
9
9
  pygit2<2,>=1.9.2
10
10
  asgiref<4,>=3.5.2
11
11
  redis<5,>=4.3.4
12
- opal-common==0.7.0
12
+ opal-common==0.7.2
13
13
  charset-normalizer<3,>=2.0.12
14
14
  idna<4,>=3.3
15
15
  typer<1,>=0.4.1
16
16
  fastapi<1,>=0.78.0
17
- fastapi_websocket_pubsub==0.3.3
17
+ fastapi_websocket_pubsub==0.3.4
18
18
  fastapi_websocket_rpc<1,>=0.1.21
19
19
  gunicorn<21,>=20.1.0
20
20
  pydantic[email]<2,>=1.9.1
File without changes
File without changes