fastpubsub 0.0.1a1__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 (34) hide show
  1. fastpubsub-0.0.1a1/PKG-INFO +196 -0
  2. fastpubsub-0.0.1a1/README.md +155 -0
  3. fastpubsub-0.0.1a1/fastpubsub/__about__.py +7 -0
  4. fastpubsub-0.0.1a1/fastpubsub/__init__.py +19 -0
  5. fastpubsub-0.0.1a1/fastpubsub/__main__.py +6 -0
  6. fastpubsub-0.0.1a1/fastpubsub/applications.py +237 -0
  7. fastpubsub-0.0.1a1/fastpubsub/broker.py +254 -0
  8. fastpubsub-0.0.1a1/fastpubsub/builder.py +69 -0
  9. fastpubsub-0.0.1a1/fastpubsub/cli/__init__.py +1 -0
  10. fastpubsub-0.0.1a1/fastpubsub/cli/main.py +158 -0
  11. fastpubsub-0.0.1a1/fastpubsub/cli/options.py +140 -0
  12. fastpubsub-0.0.1a1/fastpubsub/cli/runner.py +109 -0
  13. fastpubsub-0.0.1a1/fastpubsub/cli/utils.py +75 -0
  14. fastpubsub-0.0.1a1/fastpubsub/clients/__init__.py +1 -0
  15. fastpubsub-0.0.1a1/fastpubsub/clients/pubsub.py +309 -0
  16. fastpubsub-0.0.1a1/fastpubsub/concurrency/__init__.py +1 -0
  17. fastpubsub-0.0.1a1/fastpubsub/concurrency/manager.py +57 -0
  18. fastpubsub-0.0.1a1/fastpubsub/concurrency/tasks.py +218 -0
  19. fastpubsub-0.0.1a1/fastpubsub/concurrency/utils.py +85 -0
  20. fastpubsub-0.0.1a1/fastpubsub/datastructures.py +56 -0
  21. fastpubsub-0.0.1a1/fastpubsub/exceptions.py +23 -0
  22. fastpubsub-0.0.1a1/fastpubsub/logger.py +179 -0
  23. fastpubsub-0.0.1a1/fastpubsub/middlewares/__init__.py +1 -0
  24. fastpubsub-0.0.1a1/fastpubsub/middlewares/base.py +65 -0
  25. fastpubsub-0.0.1a1/fastpubsub/middlewares/gzip.py +48 -0
  26. fastpubsub-0.0.1a1/fastpubsub/observability.py +425 -0
  27. fastpubsub-0.0.1a1/fastpubsub/pubsub/__init__.py +1 -0
  28. fastpubsub-0.0.1a1/fastpubsub/pubsub/commands.py +65 -0
  29. fastpubsub-0.0.1a1/fastpubsub/pubsub/publisher.py +100 -0
  30. fastpubsub-0.0.1a1/fastpubsub/pubsub/subscriber.py +92 -0
  31. fastpubsub-0.0.1a1/fastpubsub/py.typed +0 -0
  32. fastpubsub-0.0.1a1/fastpubsub/router.py +325 -0
  33. fastpubsub-0.0.1a1/fastpubsub/types.py +11 -0
  34. fastpubsub-0.0.1a1/pyproject.toml +109 -0
@@ -0,0 +1,196 @@
1
+ Metadata-Version: 2.3
2
+ Name: fastpubsub
3
+ Version: 0.0.1a1
4
+ Summary: A high performance FastAPI-based message consumer framework for PubSub
5
+ Classifier: Development Status :: 5 - Production/Stable
6
+ Classifier: License :: OSI Approved :: Apache Software License
7
+ Classifier: Programming Language :: Python
8
+ Classifier: Programming Language :: Python :: Implementation :: CPython
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3 :: Only
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Classifier: Topic :: Software Development :: Libraries
17
+ Classifier: Topic :: Software Development
18
+ Classifier: Topic :: System :: Networking
19
+ Classifier: Topic :: System :: Distributed Computing
20
+ Classifier: Typing :: Typed
21
+ Classifier: Intended Audience :: Developers
22
+ Classifier: Intended Audience :: Information Technology
23
+ Classifier: Intended Audience :: System Administrators
24
+ Classifier: Environment :: Web Environment
25
+ Classifier: Framework :: AsyncIO
26
+ Classifier: Framework :: Pydantic
27
+ Classifier: Framework :: Pydantic :: 2
28
+ Requires-Dist: click>=8.2.1,<9.0.0
29
+ Requires-Dist: fastapi>=0.119.0,<1.0.0
30
+ Requires-Dist: google-api-core>=2.25.1,<3.0.0
31
+ Requires-Dist: google-cloud-pubsub>=2.31.1,<3.0.0
32
+ Requires-Dist: pydantic-settings>=2.10.1,<3.0.0
33
+ Requires-Dist: rich-toolkit>=0.15.0,<1.0.0
34
+ Requires-Dist: typer>=0.16.0,<1.0.0
35
+ Requires-Dist: uvicorn>=0.37.0,<1.0.0
36
+ Requires-Dist: httpx>=0.23.0,<1.0.0
37
+ Requires-Dist: newrelic[infinite-tracing]>=10.15.0,<11.0.0 ; extra == 'newrelic'
38
+ Requires-Python: >=3.13, <3.15
39
+ Provides-Extra: newrelic
40
+ Description-Content-Type: text/markdown
41
+
42
+ # FastPubSub
43
+
44
+ *A high performance FastAPI-based message consumer framework for PubSub.*
45
+
46
+ [//]: # (Aqui devem ir algumas tags)
47
+
48
+
49
+ ## Features
50
+
51
+
52
+ FastPubSub ia modern, high-performance framework for building modern applications that process event messages on Google PubSub. It combines the standard PubSub Python SDK with FastAPI, Pydantic and Uvicorn to provide a easy-to-use development experience.
53
+
54
+ The key features are:
55
+
56
+ - **Fast:** FastPubSub is (unironically) fast. It's built on top of [**FastAPI**](https://fastapi.tiangolo.com/), [**uvicorn**](https://uvicorn.dev/) and [**Google PubSub Python SDK**](https://github.com/googleapis/python-pubsub) for maximum performance.
57
+ - **Intuitive**: It is designed to be intuitive and easy to use, even for beginners.
58
+ - **Typed**: Providing a great editor support and less time reading docs.
59
+ - **Robust**: Get production-ready code with sensible default values avoiding shooting yourself in the foot.
60
+ - **Asynchronous:** It is built on top of anyio, which allows it to run on top of either asyncio or trio.
61
+ - **Batteries Included**: Providing its own CLI and other widely used tools such as [**pydantic**](https://docs.pydantic.dev/) for data validation, observability integrations and log contextualization.
62
+
63
+
64
+
65
+ ## Quick Start
66
+
67
+ ### Installation
68
+
69
+ FastPubSub works on Linux, macOS, Windows and most Unix-style operating systems. You can install it with pip as usual:
70
+
71
+ ```shell
72
+ pip install fastpubsub
73
+ ```
74
+
75
+ ### Writing your first application
76
+
77
+ **FastPubSub** brokers provide convenient function decorators (`@broker.subscriber`) and methods (`broker.publisher`) to allow you to delegate the actual process of:
78
+
79
+ - Creating Pub/Sub subscriptions to receive and process data from topics.
80
+ - Publishing data to other topics downstream in your message processing pipeline.
81
+
82
+ These decorators make it easy to specify the processing logic for your consumers and producers, allowing you to focus on the core business logic of your application without worrying about the underlying integration.
83
+
84
+ Also, **Pydantic**’s [`BaseModel`](https://docs.pydantic.dev/usage/models/) class allows you to define messages using a declarative syntax for sending messages downstream, making it easy to specify the fields and types of your messages.
85
+
86
+ Here is an example Python app using **FastPubSub** that consumes data from an incoming data stream and outputs two messages to another one:
87
+
88
+
89
+ ```python
90
+ # basic.py
91
+
92
+ from pydantic import BaseModel, Field
93
+ from fastpubsub import FastPubSub, PubSubBroker, Message
94
+ from fastpubsub.logger import logger
95
+
96
+ class Address(BaseModel):
97
+ street: str = Field(..., examples=["5th Avenue"])
98
+ number: str = Field(..., examples=["1548"])
99
+
100
+
101
+ broker = PubSubBroker(project_id="some-project-id")
102
+ app = FastPubSub(broker)
103
+
104
+ @broker.subscriber(
105
+ alias="my_handler",
106
+ topic_name="in_topic",
107
+ subscription_name="sub_name",
108
+ )
109
+ async def handle_message(message: Message):
110
+ logger.info(f"The message {message.id} is processed.")
111
+ await broker.publish(topic_name="out_topic", data="Hi!")
112
+
113
+ address = Address(street="Av. Flores", number="213")
114
+ await broker.publish(topic_name="out_topic", data=address)
115
+ ```
116
+
117
+
118
+
119
+ ### Running the application
120
+
121
+ Before running the command make sure to set one of the variables (mutually exclusive):
122
+
123
+ 1. **Running PubSub on Cloud**: The environment variable `GOOGLE_APPLICATION_CREDENTIALS` with the path of the service-account on your system.
124
+ 2. **Running PubSub Emulator**: The environment variable `PUBSUB_EMULATOR_HOST` with host:port of your local PubSub emulator.
125
+
126
+
127
+ ---
128
+
129
+ After that, the application can be started using built-in **FastPubSub** CLI command. It is embedded in the library and its a core part of the system.
130
+
131
+ To run the service, use the **FastPubSub** embedded CLI. Just execute the command ``run`` and pass the module (in this case, the file where the app implementation is located) and the app symbol to the command.
132
+
133
+ ```bash
134
+ fastpubsub run basic:app
135
+ ```
136
+
137
+ After running the command, you should see the following output:
138
+
139
+
140
+ ``` shell
141
+ 2025-10-13 15:23:59,550 | INFO | 97527:133552019097408 | runner:run:55 | FastPubSub app starting...
142
+ 2025-10-13 15:23:59,696 | INFO | 97527:133552019097408 | tasks:start:74 | The handle_message handler is waiting for messages.
143
+ ```
144
+
145
+ Also, **FastPubSub** provides you with a great hot reload feature to improve your Development Experience
146
+
147
+ ``` shell
148
+ fastpubsub run basic:app --reload
149
+ ```
150
+
151
+ And multiprocessing horizontal scaling feature as well:
152
+
153
+ ``` shell
154
+ fastpubsub run basic:app --workers 3
155
+ ```
156
+
157
+ You can learn more about **CLI** features [here](docs/learn/tutorial/07.cli.md).
158
+
159
+
160
+ ## Further Documentation
161
+
162
+ 1. [Features](docs/features/00.index.md)
163
+ 2. [Getting Started](docs/getting-started/00.index.md)
164
+ 3. [Learn](docs/learn/00.index.md)
165
+ 1. [Introduction to Google PubSub](docs/learn/01.intro-pubsub.md)
166
+ 2. [Introduction to Async/Await](docs/learn/02.intro-async-await.md)
167
+ 3. [Introduction to Virtual Environments](docs/learn/03.intro-venv.md)
168
+ 4. [Tutorial: User Guide](docs/learn/tutorial/00.index.md)
169
+ 1. [Subscription Basics](docs/learn/tutorial/01.subscription.md)
170
+ 2. [Publishing Basics](docs/learn/tutorial/02.publishing.md)
171
+ 3. [Lifespan and Hooks](docs/learn/tutorial/03.lifespan.md)
172
+ 4. [Acknowledgement](docs/learn/tutorial/04.acknowledgement.md)
173
+ 5. [Routers (and Hierarchy)](docs/learn/tutorial/05.routers.md)
174
+ 6. [Middlewares (and Hierarchy)](docs/learn/tutorial/06.middlewares.md)
175
+ 7. [Command line Interface (CLI)](docs/learn/tutorial/07.cli.md)
176
+ 8. [Integrations](docs/learn/tutorial/integrations/00.index.md)
177
+ 1. [FastAPI](docs/learn/tutorial/integrations/01.fastapi.md)
178
+ 2. [Observability](docs/learn/tutorial/integrations/02.observability.md)
179
+ 3. [Logging](docs/learn/tutorial/integrations/03.logger.md)
180
+ 4. [Application Probes](docs/learn/tutorial/integrations/04.probes.md)
181
+ 5. [Deployment Guide](docs/learn/deployment/00.index.md)
182
+ 1. [On Virtual Machines](docs/learn/deployment/01.vm-guide.md)
183
+ 2. [On Kubernetes](docs/learn/deployment/02.k8-guide.md)
184
+
185
+
186
+ ## Contact
187
+
188
+ Please stay in touch by:
189
+
190
+ Sending a email at sandro-matheus@hotmail.com.
191
+
192
+ Sending a message on my [linkedin](www.linkedin.com/in/matheusvnm).
193
+
194
+
195
+ ## License
196
+ This project is licensed under the terms of the Apache 2.0 license.
@@ -0,0 +1,155 @@
1
+ # FastPubSub
2
+
3
+ *A high performance FastAPI-based message consumer framework for PubSub.*
4
+
5
+ [//]: # (Aqui devem ir algumas tags)
6
+
7
+
8
+ ## Features
9
+
10
+
11
+ FastPubSub ia modern, high-performance framework for building modern applications that process event messages on Google PubSub. It combines the standard PubSub Python SDK with FastAPI, Pydantic and Uvicorn to provide a easy-to-use development experience.
12
+
13
+ The key features are:
14
+
15
+ - **Fast:** FastPubSub is (unironically) fast. It's built on top of [**FastAPI**](https://fastapi.tiangolo.com/), [**uvicorn**](https://uvicorn.dev/) and [**Google PubSub Python SDK**](https://github.com/googleapis/python-pubsub) for maximum performance.
16
+ - **Intuitive**: It is designed to be intuitive and easy to use, even for beginners.
17
+ - **Typed**: Providing a great editor support and less time reading docs.
18
+ - **Robust**: Get production-ready code with sensible default values avoiding shooting yourself in the foot.
19
+ - **Asynchronous:** It is built on top of anyio, which allows it to run on top of either asyncio or trio.
20
+ - **Batteries Included**: Providing its own CLI and other widely used tools such as [**pydantic**](https://docs.pydantic.dev/) for data validation, observability integrations and log contextualization.
21
+
22
+
23
+
24
+ ## Quick Start
25
+
26
+ ### Installation
27
+
28
+ FastPubSub works on Linux, macOS, Windows and most Unix-style operating systems. You can install it with pip as usual:
29
+
30
+ ```shell
31
+ pip install fastpubsub
32
+ ```
33
+
34
+ ### Writing your first application
35
+
36
+ **FastPubSub** brokers provide convenient function decorators (`@broker.subscriber`) and methods (`broker.publisher`) to allow you to delegate the actual process of:
37
+
38
+ - Creating Pub/Sub subscriptions to receive and process data from topics.
39
+ - Publishing data to other topics downstream in your message processing pipeline.
40
+
41
+ These decorators make it easy to specify the processing logic for your consumers and producers, allowing you to focus on the core business logic of your application without worrying about the underlying integration.
42
+
43
+ Also, **Pydantic**’s [`BaseModel`](https://docs.pydantic.dev/usage/models/) class allows you to define messages using a declarative syntax for sending messages downstream, making it easy to specify the fields and types of your messages.
44
+
45
+ Here is an example Python app using **FastPubSub** that consumes data from an incoming data stream and outputs two messages to another one:
46
+
47
+
48
+ ```python
49
+ # basic.py
50
+
51
+ from pydantic import BaseModel, Field
52
+ from fastpubsub import FastPubSub, PubSubBroker, Message
53
+ from fastpubsub.logger import logger
54
+
55
+ class Address(BaseModel):
56
+ street: str = Field(..., examples=["5th Avenue"])
57
+ number: str = Field(..., examples=["1548"])
58
+
59
+
60
+ broker = PubSubBroker(project_id="some-project-id")
61
+ app = FastPubSub(broker)
62
+
63
+ @broker.subscriber(
64
+ alias="my_handler",
65
+ topic_name="in_topic",
66
+ subscription_name="sub_name",
67
+ )
68
+ async def handle_message(message: Message):
69
+ logger.info(f"The message {message.id} is processed.")
70
+ await broker.publish(topic_name="out_topic", data="Hi!")
71
+
72
+ address = Address(street="Av. Flores", number="213")
73
+ await broker.publish(topic_name="out_topic", data=address)
74
+ ```
75
+
76
+
77
+
78
+ ### Running the application
79
+
80
+ Before running the command make sure to set one of the variables (mutually exclusive):
81
+
82
+ 1. **Running PubSub on Cloud**: The environment variable `GOOGLE_APPLICATION_CREDENTIALS` with the path of the service-account on your system.
83
+ 2. **Running PubSub Emulator**: The environment variable `PUBSUB_EMULATOR_HOST` with host:port of your local PubSub emulator.
84
+
85
+
86
+ ---
87
+
88
+ After that, the application can be started using built-in **FastPubSub** CLI command. It is embedded in the library and its a core part of the system.
89
+
90
+ To run the service, use the **FastPubSub** embedded CLI. Just execute the command ``run`` and pass the module (in this case, the file where the app implementation is located) and the app symbol to the command.
91
+
92
+ ```bash
93
+ fastpubsub run basic:app
94
+ ```
95
+
96
+ After running the command, you should see the following output:
97
+
98
+
99
+ ``` shell
100
+ 2025-10-13 15:23:59,550 | INFO | 97527:133552019097408 | runner:run:55 | FastPubSub app starting...
101
+ 2025-10-13 15:23:59,696 | INFO | 97527:133552019097408 | tasks:start:74 | The handle_message handler is waiting for messages.
102
+ ```
103
+
104
+ Also, **FastPubSub** provides you with a great hot reload feature to improve your Development Experience
105
+
106
+ ``` shell
107
+ fastpubsub run basic:app --reload
108
+ ```
109
+
110
+ And multiprocessing horizontal scaling feature as well:
111
+
112
+ ``` shell
113
+ fastpubsub run basic:app --workers 3
114
+ ```
115
+
116
+ You can learn more about **CLI** features [here](docs/learn/tutorial/07.cli.md).
117
+
118
+
119
+ ## Further Documentation
120
+
121
+ 1. [Features](docs/features/00.index.md)
122
+ 2. [Getting Started](docs/getting-started/00.index.md)
123
+ 3. [Learn](docs/learn/00.index.md)
124
+ 1. [Introduction to Google PubSub](docs/learn/01.intro-pubsub.md)
125
+ 2. [Introduction to Async/Await](docs/learn/02.intro-async-await.md)
126
+ 3. [Introduction to Virtual Environments](docs/learn/03.intro-venv.md)
127
+ 4. [Tutorial: User Guide](docs/learn/tutorial/00.index.md)
128
+ 1. [Subscription Basics](docs/learn/tutorial/01.subscription.md)
129
+ 2. [Publishing Basics](docs/learn/tutorial/02.publishing.md)
130
+ 3. [Lifespan and Hooks](docs/learn/tutorial/03.lifespan.md)
131
+ 4. [Acknowledgement](docs/learn/tutorial/04.acknowledgement.md)
132
+ 5. [Routers (and Hierarchy)](docs/learn/tutorial/05.routers.md)
133
+ 6. [Middlewares (and Hierarchy)](docs/learn/tutorial/06.middlewares.md)
134
+ 7. [Command line Interface (CLI)](docs/learn/tutorial/07.cli.md)
135
+ 8. [Integrations](docs/learn/tutorial/integrations/00.index.md)
136
+ 1. [FastAPI](docs/learn/tutorial/integrations/01.fastapi.md)
137
+ 2. [Observability](docs/learn/tutorial/integrations/02.observability.md)
138
+ 3. [Logging](docs/learn/tutorial/integrations/03.logger.md)
139
+ 4. [Application Probes](docs/learn/tutorial/integrations/04.probes.md)
140
+ 5. [Deployment Guide](docs/learn/deployment/00.index.md)
141
+ 1. [On Virtual Machines](docs/learn/deployment/01.vm-guide.md)
142
+ 2. [On Kubernetes](docs/learn/deployment/02.k8-guide.md)
143
+
144
+
145
+ ## Contact
146
+
147
+ Please stay in touch by:
148
+
149
+ Sending a email at sandro-matheus@hotmail.com.
150
+
151
+ Sending a message on my [linkedin](www.linkedin.com/in/matheusvnm).
152
+
153
+
154
+ ## License
155
+ This project is licensed under the terms of the Apache 2.0 license.
@@ -0,0 +1,7 @@
1
+ """Simple and fast framework to create message brokers based microservices."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("fastpubsub")
6
+
7
+ SERVICE_NAME = f"fastpubsub-{__version__}"
@@ -0,0 +1,19 @@
1
+ """A high performance FastAPI-based message consumer framework for Google PubSub."""
2
+
3
+ from fastpubsub.applications import FastPubSub
4
+ from fastpubsub.broker import PubSubBroker
5
+ from fastpubsub.datastructures import Message
6
+ from fastpubsub.middlewares.base import BaseMiddleware
7
+ from fastpubsub.pubsub.publisher import Publisher
8
+ from fastpubsub.pubsub.subscriber import Subscriber
9
+ from fastpubsub.router import PubSubRouter
10
+
11
+ __all__ = [
12
+ "FastPubSub",
13
+ "PubSubBroker",
14
+ "PubSubRouter",
15
+ "Publisher",
16
+ "Subscriber",
17
+ "BaseMiddleware",
18
+ "Message",
19
+ ]
@@ -0,0 +1,6 @@
1
+ """FastPubSub CLI entrypoint."""
2
+
3
+ from fastpubsub.cli.main import app
4
+
5
+ if __name__ == "__main__":
6
+ app(prog_name="fastpubsub")
@@ -0,0 +1,237 @@
1
+ """FastPubSub application and lifecycle management."""
2
+
3
+ from collections.abc import AsyncGenerator, AsyncIterator, Sequence
4
+ from contextlib import asynccontextmanager
5
+ from typing import Any
6
+
7
+ from fastapi import FastAPI, Request
8
+ from fastapi.responses import JSONResponse
9
+ from pydantic import ConfigDict, validate_call
10
+ from starlette.applications import Starlette
11
+ from starlette.status import HTTP_200_OK, HTTP_500_INTERNAL_SERVER_ERROR
12
+
13
+ from fastpubsub.broker import PubSubBroker
14
+ from fastpubsub.concurrency.utils import ensure_async_callable_function
15
+ from fastpubsub.logger import logger
16
+ from fastpubsub.observability import get_apm_provider
17
+ from fastpubsub.types import NoArgAsyncCallable
18
+
19
+
20
+ class Application:
21
+ """Manages the lifecycle of a FastPubSub application."""
22
+
23
+ def __init__(
24
+ self,
25
+ broker: PubSubBroker,
26
+ on_startup: Sequence[NoArgAsyncCallable] | None = None,
27
+ on_shutdown: Sequence[NoArgAsyncCallable] | None = None,
28
+ after_startup: Sequence[NoArgAsyncCallable] | None = None,
29
+ after_shutdown: Sequence[NoArgAsyncCallable] | None = None,
30
+ ):
31
+ """Initializes the Application.
32
+
33
+ Args:
34
+ broker: The PubSubBroker instance.
35
+ on_startup: A sequence of callables to run on startup.
36
+ on_shutdown: A sequence of callables to run on shutdown.
37
+ after_startup: A sequence of callables to run after startup.
38
+ after_shutdown: A sequence of callables to run after shutdown.
39
+ """
40
+ self.broker = broker
41
+ self.apm = get_apm_provider()
42
+
43
+ self._on_startup: list[NoArgAsyncCallable] = []
44
+ if on_startup and isinstance(on_startup, Sequence):
45
+ for func in on_startup:
46
+ self.on_startup(func)
47
+
48
+ self._on_shutdown: list[NoArgAsyncCallable] = []
49
+ if on_shutdown and isinstance(on_shutdown, Sequence):
50
+ for func in on_shutdown:
51
+ self.on_shutdown(func)
52
+
53
+ self._after_startup: list[NoArgAsyncCallable] = []
54
+ if after_startup and isinstance(after_startup, Sequence):
55
+ for func in after_startup:
56
+ self.after_startup(func)
57
+
58
+ self._after_shutdown: list[NoArgAsyncCallable] = []
59
+ if after_shutdown and isinstance(after_shutdown, Sequence):
60
+ for func in after_shutdown:
61
+ self.after_shutdown(func)
62
+
63
+ @validate_call(config=ConfigDict(strict=True))
64
+ def on_startup(self, func: NoArgAsyncCallable) -> NoArgAsyncCallable:
65
+ """Decorator to register a function to run on startup.
66
+
67
+ Args:
68
+ func: The function to run on startup.
69
+
70
+ Returns:
71
+ The decorated function.
72
+ """
73
+ ensure_async_callable_function(func)
74
+ self._on_startup.append(func)
75
+ return func
76
+
77
+ @validate_call(config=ConfigDict(strict=True))
78
+ def on_shutdown(self, func: NoArgAsyncCallable) -> NoArgAsyncCallable:
79
+ """Decorator to register a function to run on shutdown.
80
+
81
+ Args:
82
+ func: The function to run on shutdown.
83
+
84
+ Returns:
85
+ The decorated function.
86
+ """
87
+ ensure_async_callable_function(func)
88
+ self._on_shutdown.append(func)
89
+ return func
90
+
91
+ @validate_call(config=ConfigDict(strict=True))
92
+ def after_startup(self, func: NoArgAsyncCallable) -> NoArgAsyncCallable:
93
+ """Decorator to register a function to run after startup.
94
+
95
+ Args:
96
+ func: The function to run after startup.
97
+
98
+ Returns:
99
+ The decorated function.
100
+ """
101
+ ensure_async_callable_function(func)
102
+ self._after_startup.append(func)
103
+ return func
104
+
105
+ @validate_call(config=ConfigDict(strict=True))
106
+ def after_shutdown(self, func: NoArgAsyncCallable) -> NoArgAsyncCallable:
107
+ """Decorator to register a function to run after shutdown.
108
+
109
+ Args:
110
+ func: The function to run after shutdown.
111
+
112
+ Returns:
113
+ The decorated function.
114
+ """
115
+ ensure_async_callable_function(func)
116
+ self._after_shutdown.append(func)
117
+ return func
118
+
119
+ # V1: Create a contextualizer
120
+ async def _start(self) -> None:
121
+ self.apm.start()
122
+ with self.apm.start_trace(name="start"):
123
+ context = {
124
+ "span_id": self.apm.get_span_id(),
125
+ "trace_id": self.apm.get_trace_id(),
126
+ }
127
+
128
+ with logger.contextualize(**context):
129
+ async with self._start_hooks():
130
+ await self.broker.start()
131
+
132
+ @asynccontextmanager
133
+ async def _start_hooks(self) -> AsyncIterator[None]:
134
+ for func in self._on_startup:
135
+ await func()
136
+
137
+ yield
138
+
139
+ for func in self._after_startup:
140
+ await func()
141
+
142
+ async def _shutdown(self) -> None:
143
+ with self.apm.start_trace(name="shutdown"):
144
+ context = {
145
+ "span_id": self.apm.get_span_id(),
146
+ "trace_id": self.apm.get_trace_id(),
147
+ }
148
+ with logger.contextualize(**context):
149
+ async with self._shutdown_hooks():
150
+ await self.broker.shutdown()
151
+
152
+ self.apm.shutdown()
153
+
154
+ @asynccontextmanager
155
+ async def _shutdown_hooks(self) -> AsyncIterator[None]:
156
+ for func in self._on_shutdown:
157
+ await func()
158
+
159
+ yield
160
+
161
+ for func in self._after_shutdown:
162
+ await func()
163
+
164
+
165
+ class FastPubSub(FastAPI, Application):
166
+ """A FastAPI integration application for managing Pub/Sub consumers."""
167
+
168
+ def __init__(
169
+ self,
170
+ broker: PubSubBroker,
171
+ *,
172
+ on_startup: Sequence[NoArgAsyncCallable] | None = None,
173
+ on_shutdown: Sequence[NoArgAsyncCallable] | None = None,
174
+ after_startup: Sequence[NoArgAsyncCallable] | None = None,
175
+ after_shutdown: Sequence[NoArgAsyncCallable] | None = None,
176
+ liveness_url: str = "/consumers/alive",
177
+ readiness_url: str = "/consumers/ready",
178
+ **extras: Any,
179
+ ):
180
+ """Initializes the FastPubSub application.
181
+
182
+ Args:
183
+ broker: The PubSubBroker instance.
184
+ on_startup: A sequence of callables to run on startup.
185
+ on_shutdown: A sequence of callables to run on shutdown.
186
+ after_startup: A sequence of callables to run after startup.
187
+ after_shutdown: A sequence of callables to run after shutdown.
188
+ liveness_url: A url path for the readiness endpoint.
189
+ readiness_url: A url path for the readiness endpoint.
190
+ **extras: Extra arguments to pass to the FastAPI constructor.
191
+ """
192
+ self.lifespan_context = extras.pop("lifespan", None)
193
+ super().__init__(
194
+ lifespan=self._run,
195
+ **extras,
196
+ )
197
+
198
+ super(Starlette, self).__init__(
199
+ broker,
200
+ on_startup=on_startup,
201
+ on_shutdown=on_shutdown,
202
+ after_startup=after_startup,
203
+ after_shutdown=after_shutdown,
204
+ )
205
+
206
+ self.add_api_route(path=liveness_url, endpoint=self._get_liveness, methods=["GET"])
207
+ self.add_api_route(path=readiness_url, endpoint=self._get_readiness, methods=["GET"])
208
+
209
+ @asynccontextmanager
210
+ async def _run(self, app: "FastPubSub") -> AsyncGenerator[None]:
211
+ if not self.lifespan_context:
212
+ await self._start()
213
+ yield
214
+ await self._shutdown()
215
+ else:
216
+ async with self.lifespan_context(app):
217
+ await self._start()
218
+ yield
219
+ await self._shutdown()
220
+
221
+ async def _get_liveness(self, _: Request) -> JSONResponse:
222
+ alive = await self.broker.alive()
223
+
224
+ status_code = HTTP_200_OK
225
+ if not alive:
226
+ status_code = HTTP_500_INTERNAL_SERVER_ERROR
227
+
228
+ return JSONResponse(content={"alive": alive}, status_code=status_code)
229
+
230
+ async def _get_readiness(self, _: Request) -> JSONResponse:
231
+ ready = await self.broker.ready()
232
+
233
+ status_code = HTTP_200_OK
234
+ if not ready:
235
+ status_code = HTTP_500_INTERNAL_SERVER_ERROR
236
+
237
+ return JSONResponse(content={"ready": ready}, status_code=status_code)