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.
- fastpubsub-0.0.1a1/PKG-INFO +196 -0
- fastpubsub-0.0.1a1/README.md +155 -0
- fastpubsub-0.0.1a1/fastpubsub/__about__.py +7 -0
- fastpubsub-0.0.1a1/fastpubsub/__init__.py +19 -0
- fastpubsub-0.0.1a1/fastpubsub/__main__.py +6 -0
- fastpubsub-0.0.1a1/fastpubsub/applications.py +237 -0
- fastpubsub-0.0.1a1/fastpubsub/broker.py +254 -0
- fastpubsub-0.0.1a1/fastpubsub/builder.py +69 -0
- fastpubsub-0.0.1a1/fastpubsub/cli/__init__.py +1 -0
- fastpubsub-0.0.1a1/fastpubsub/cli/main.py +158 -0
- fastpubsub-0.0.1a1/fastpubsub/cli/options.py +140 -0
- fastpubsub-0.0.1a1/fastpubsub/cli/runner.py +109 -0
- fastpubsub-0.0.1a1/fastpubsub/cli/utils.py +75 -0
- fastpubsub-0.0.1a1/fastpubsub/clients/__init__.py +1 -0
- fastpubsub-0.0.1a1/fastpubsub/clients/pubsub.py +309 -0
- fastpubsub-0.0.1a1/fastpubsub/concurrency/__init__.py +1 -0
- fastpubsub-0.0.1a1/fastpubsub/concurrency/manager.py +57 -0
- fastpubsub-0.0.1a1/fastpubsub/concurrency/tasks.py +218 -0
- fastpubsub-0.0.1a1/fastpubsub/concurrency/utils.py +85 -0
- fastpubsub-0.0.1a1/fastpubsub/datastructures.py +56 -0
- fastpubsub-0.0.1a1/fastpubsub/exceptions.py +23 -0
- fastpubsub-0.0.1a1/fastpubsub/logger.py +179 -0
- fastpubsub-0.0.1a1/fastpubsub/middlewares/__init__.py +1 -0
- fastpubsub-0.0.1a1/fastpubsub/middlewares/base.py +65 -0
- fastpubsub-0.0.1a1/fastpubsub/middlewares/gzip.py +48 -0
- fastpubsub-0.0.1a1/fastpubsub/observability.py +425 -0
- fastpubsub-0.0.1a1/fastpubsub/pubsub/__init__.py +1 -0
- fastpubsub-0.0.1a1/fastpubsub/pubsub/commands.py +65 -0
- fastpubsub-0.0.1a1/fastpubsub/pubsub/publisher.py +100 -0
- fastpubsub-0.0.1a1/fastpubsub/pubsub/subscriber.py +92 -0
- fastpubsub-0.0.1a1/fastpubsub/py.typed +0 -0
- fastpubsub-0.0.1a1/fastpubsub/router.py +325 -0
- fastpubsub-0.0.1a1/fastpubsub/types.py +11 -0
- 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,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,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)
|