beanqueue 0.1.2__tar.gz → 0.2.0__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. {beanqueue-0.1.2 → beanqueue-0.2.0}/PKG-INFO +122 -31
  2. {beanqueue-0.1.2 → beanqueue-0.2.0}/README.md +120 -29
  3. beanqueue-0.2.0/bq/__init__.py +10 -0
  4. beanqueue-0.2.0/bq/app.py +260 -0
  5. beanqueue-0.2.0/bq/cmds/create_tables.py +26 -0
  6. beanqueue-0.2.0/bq/cmds/process.py +23 -0
  7. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/cmds/submit.py +10 -10
  8. beanqueue-0.2.0/bq/cmds/utils.py +14 -0
  9. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/config.py +9 -0
  10. beanqueue-0.2.0/bq/constants.py +4 -0
  11. beanqueue-0.2.0/bq/events.py +3 -0
  12. beanqueue-0.2.0/bq/models/__init__.py +8 -0
  13. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/models/task.py +40 -26
  14. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/models/worker.py +25 -13
  15. beanqueue-0.2.0/bq/processors/processor.py +70 -0
  16. beanqueue-0.2.0/bq/processors/registry.py +47 -0
  17. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/services/dispatch.py +14 -11
  18. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/services/worker.py +26 -12
  19. beanqueue-0.2.0/bq/utils.py +8 -0
  20. {beanqueue-0.1.2 → beanqueue-0.2.0}/pyproject.toml +2 -2
  21. beanqueue-0.1.2/bq/cmds/create_tables.py +0 -25
  22. beanqueue-0.1.2/bq/cmds/process.py +0 -178
  23. beanqueue-0.1.2/bq/container.py +0 -54
  24. beanqueue-0.1.2/bq/models/__init__.py +0 -4
  25. beanqueue-0.1.2/bq/processors/registry.py +0 -136
  26. beanqueue-0.1.2/bq/services/__init__.py +0 -0
  27. {beanqueue-0.1.2 → beanqueue-0.2.0}/LICENSE +0 -0
  28. {beanqueue-0.1.2/bq → beanqueue-0.2.0/bq/cmds}/__init__.py +0 -0
  29. {beanqueue-0.1.2/bq/cmds → beanqueue-0.2.0/bq/db}/__init__.py +0 -0
  30. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/db/base.py +0 -0
  31. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/db/session.py +0 -0
  32. {beanqueue-0.1.2 → beanqueue-0.2.0}/bq/models/helpers.py +0 -0
  33. {beanqueue-0.1.2/bq/db → beanqueue-0.2.0/bq/processors}/__init__.py +0 -0
  34. {beanqueue-0.1.2/bq/processors → beanqueue-0.2.0/bq/services}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: beanqueue
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: BeanQueue or BQ for short, PostgreSQL SKIP LOCK based worker queue library
5
5
  License: MIT
6
6
  Author: Fang-Pen Lin
@@ -10,8 +10,8 @@ Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
+ Requires-Dist: blinker (>=1.8.2,<2.0.0)
13
14
  Requires-Dist: click (>=8.1.7,<9.0.0)
14
- Requires-Dist: dependency-injector (>=4.41.0,<5.0.0)
15
15
  Requires-Dist: pg-activity (>=3.5.1,<4.0.0)
16
16
  Requires-Dist: pydantic-settings (>=2.2.1,<3.0.0)
17
17
  Requires-Dist: sqlalchemy (>=2.0.30,<3.0.0)
@@ -19,7 +19,7 @@ Requires-Dist: venusian (>=3.1.0,<4.0.0)
19
19
  Description-Content-Type: text/markdown
20
20
 
21
21
  # BeanQueue [![CircleCI](https://dl.circleci.com/status-badge/img/gh/LaunchPlatform/bq/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/LaunchPlatform/beanhub-extract/tree/master)
22
- BeanQueue, a lightweight worker queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/), [PostgreSQL SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
22
+ BeanQueue, a lightweight worker queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/), PostgreSQL [SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
23
23
 
24
24
  **Notice**: Still in its early stage, we built this for [BeanHub](https://beanhub.io)'s internal usage. May change rapidly. Use at your own risk for now.
25
25
 
@@ -29,7 +29,7 @@ BeanQueue, a lightweight worker queue framework based on [SQLAlchemy](https://ww
29
29
  - **Easy-to-deploy**: Only rely on PostgreSQL
30
30
  - **Easy-to-use**: Provide command line tools for processing tasks, also helpers for generating tasks models
31
31
  - **Auto-notify**: Notify will automatically be generated and send for inserted or update tasks
32
- - **Worker heartbeat and auto-reschedule**: Each worker keeps updating heartbeat, if one is dead, the others will reschedule the tasks
32
+ - **Worker heartbeat and auto-reschedule**: Each worker keeps updating heartbeat, if one is found dead, the others will reschedule the tasks
33
33
  - **Customizable**: Use it as an library and build your own worker queue
34
34
  - **Native DB operations**: Commit your tasks with other db entries altogether without worrying about data inconsistent issue
35
35
 
@@ -46,14 +46,15 @@ You can define a task processor like this
46
46
  ```python
47
47
  from sqlalchemy.orm import Session
48
48
 
49
- from bq.processors.registry import processor
50
- from bq import models
51
- from .. import my_models
49
+ import bq
50
+ from .. import models
52
51
  from .. import image_utils
53
52
 
54
- @processor(channel="images")
55
- def resize_image(db: Session, task: models.Task, width: int, height: int):
56
- image = db.query(my_models.Image).filter(my_models.Image.task == task).one()
53
+ app = bq.BeanQueue()
54
+
55
+ @app.processor(channel="images")
56
+ def resize_image(db: Session, task: bq.Task, width: int, height: int):
57
+ image = db.query(models.Image).filter(models.Image.task == task).one()
57
58
  image_utils.resize(image, size=(width, height))
58
59
  db.add(image)
59
60
  # by default the `processor` decorator has `auto_complete` flag turns on,
@@ -62,22 +63,23 @@ def resize_image(db: Session, task: models.Task, width: int, height: int):
62
63
 
63
64
  The `db` and `task` keyword arguments are optional.
64
65
  If you don't need to access the task object, you can simply define the function without these two parameters.
66
+ We also provide an optional `savepoint` argument in case if you want to rollback database changes you made.
65
67
 
66
- To submit a task, you can either use `bq.models.Task` model object to construct the task object, insert into the
68
+ To submit a task, you can either use `bq.Task` model object to construct the task object, insert into the
67
69
  database session and commit.
68
70
 
69
71
  ```python
70
- from bq import models
72
+ import bq
71
73
  from .db import Session
72
- from .. import my_models
74
+ from .. import models
73
75
 
74
76
  db = Session()
75
- task = models.Task(
77
+ task = bq.Task(
76
78
  channel="files",
77
79
  module="my_pkgs.files.processors",
78
80
  name="upload_to_s3_for_backup",
79
81
  )
80
- file = my_models.File(
82
+ file = models.File(
81
83
  task=task,
82
84
  blob_name="...",
83
85
  )
@@ -112,6 +114,7 @@ To run the worker, you can do this:
112
114
  BQ_PROCESSOR_PACKAGES='["my_pkgs.processors"]' python -m bq.cmds.process images
113
115
  ```
114
116
 
117
+ The `BQ_PROCESSOR_PACKAGES` is a JSON list contains the Python packages where you define your processors (the functions you decorated with `bq.processors.registry.processor`).
115
118
  To submit a task for testing purpose, you can do
116
119
 
117
120
  ```bash
@@ -130,29 +133,116 @@ Configurations can be modified by setting environment variables with `BQ_` prefi
130
133
  For example, to set the python packages to scan for processors, you can set `BQ_PROCESSOR_PACKAGES`.
131
134
  To change the PostgreSQL database to connect to, you can set `BQ_DATABASE_URL`.
132
135
  The complete definition of configurations can be found at the [bq/config.py](bq/config.py) module.
133
- For now, the configurations only affect command line tools.
134
136
 
135
- If you want to configure BeanQueue programmatically for the command lines, you can override our [dependency-injector](https://python-dependency-injector.ets-labs.org/)'s container defined at [bq/container.py](bq/container.py) and call the command function manually.
137
+ If you want to configure BeanQueue programmatically, you can pass in `Config` object to the `bq.BeanQueue` object when creating.
136
138
  For example:
137
139
 
138
140
  ```python
139
- import bq.cmds.process
140
- from bq.container import Container
141
- from bq.config import Config
142
-
143
- container = Container()
144
- container.wire(modules=[bq.cmds.process])
145
- with container.config.override(
146
- Config(
147
- PROCESSOR_PACKAGES=["my_pkgs.processors"],
148
- DATABASE_URL="postgresql://...",
149
- BATCH_SIZE=10,
141
+ import bq
142
+ from .my_config import config
143
+
144
+ container = bq.Container()
145
+ container.wire(packages=[bq])
146
+ config = bq.Config(
147
+ PROCESSOR_PACKAGES=["my_pkgs.processors"],
148
+ DATABASE_URL=str(config.DATABASE_URL),
149
+ BATCH_SIZE=10,
150
+ )
151
+ app = bq.BeanQueue(config=config)
152
+ ```
153
+
154
+ Then you can pass `--app` argument pointing to the app object to the process command like this:
155
+
156
+ ```bash
157
+ python -m bq.cmds.process -a my_pkgs.bq.app images
158
+ ```
159
+
160
+ Or if you prefer to define your own process command, you can also call `process_tasks` of the `BeanQueue` object directly like this:
161
+
162
+ ```python
163
+ app.process_tasks(channels=("images",))
164
+ ```
165
+
166
+ ### Define your own tables
167
+
168
+ BeanQueue is designed to be as customizable as much as possible.
169
+ Of course, you can define your own SQLAlchemy model instead of using the ones we provided.
170
+
171
+ To make defining your own `Task` model or `Worker` model much easier, you can use our mixin classes:
172
+
173
+ - `bq.TaskModelMixin`: provides task model columns
174
+ - `bq.TaskModelRefWorkerMixin`: provides foreign key column and relationship to `bq.Worker`
175
+ - `bq.WorkerModelMixin`: provides worker model columns
176
+ - `bq.WorkerRefMixin`: provides relationship to `bq.Task`
177
+
178
+ Here's an example for defining your own Task model:
179
+
180
+ ```python
181
+ import uuid
182
+
183
+ import bq
184
+ from sqlalchemy import ForeignKey
185
+ from sqlalchemy.dialects.postgresql import UUID
186
+ from sqlalchemy.orm import Mapped
187
+ from sqlalchemy.orm import mapped_column
188
+ from sqlalchemy.orm import relationship
189
+
190
+ from .base_class import Base
191
+
192
+
193
+ class Task(bq.TaskModelMixin, Base):
194
+ __tablename__ = "task"
195
+ worker_id: Mapped[uuid.UUID] = mapped_column(
196
+ UUID(as_uuid=True),
197
+ ForeignKey("worker.id", onupdate="CASCADE"),
198
+ nullable=True,
199
+ index=True,
200
+ )
201
+
202
+ worker: Mapped["Worker"] = relationship(
203
+ "Worker", back_populates="tasks", uselist=False
150
204
  )
151
- ):
152
- bq.cmds.process.process_tasks(channels=("images",))
153
205
  ```
154
206
 
155
- Many other behaviors of this framework can also be modified by overriding the container defined at [bq/container.py](bq/container.py).
207
+ To make task insert and update with state changing to `PENDING` send out NOTIFY "channel" statement automatically, you can also use `bq.models.task.listen_events` helper to register our SQLAlchemy event handlers automatically like this
208
+
209
+ ```python
210
+ from bq.models.task import listen_events
211
+ listen_events(Task)
212
+ ```
213
+
214
+ You just see how easy it is to define your Task model. Now, here's an example for defining your own Worker model:
215
+
216
+ ```python
217
+ import bq
218
+ from sqlalchemy.orm import Mapped
219
+ from sqlalchemy.orm import relationship
220
+
221
+ from .base_class import Base
222
+
223
+
224
+ class Worker(bq.WorkerModelMixin, Base):
225
+ __tablename__ = "worker"
226
+
227
+ tasks: Mapped[list["Task"]] = relationship(
228
+ "Task",
229
+ back_populates="worker",
230
+ cascade="all,delete",
231
+ order_by="Task.created_at",
232
+ )
233
+ ```
234
+
235
+ With the model class ready, you only need to change the `TASK_MODEL` and `WORKER_MODEL` of `Config` to the full Python module name plus the class name like this.
236
+
237
+ ```python
238
+ import bq
239
+ config = bq.Config(
240
+ TASK_MODEL="my_pkgs.models.Task",
241
+ WORKER_MODEL="my_pkgs.models.Worker",
242
+ # ... other configs
243
+ )
244
+ app = bq.BeanQueue(config)
245
+ ```
156
246
 
157
247
  ## Why?
158
248
 
@@ -230,6 +320,7 @@ A modern accounting book service based on the most popular open source version c
230
320
 
231
321
  - [solid_queue](https://github.com/rails/solid_queue)
232
322
  - [postgres-tq](https://github.com/flix-tech/postgres-tq)
323
+ - [pq](https://github.com/malthe/pq/)
233
324
  - [PgQueuer](https://github.com/janbjorge/PgQueuer)
234
325
  - [hatchet](https://github.com/hatchet-dev/hatchet)
235
326
 
@@ -1,5 +1,5 @@
1
1
  # BeanQueue [![CircleCI](https://dl.circleci.com/status-badge/img/gh/LaunchPlatform/bq/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/LaunchPlatform/beanhub-extract/tree/master)
2
- BeanQueue, a lightweight worker queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/), [PostgreSQL SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
2
+ BeanQueue, a lightweight worker queue framework based on [SQLAlchemy](https://www.sqlalchemy.org/), PostgreSQL [SKIP LOCKED queries](https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/) and [NOTIFY](https://www.postgresql.org/docs/current/sql-notify.html) / [LISTEN](https://www.postgresql.org/docs/current/sql-listen.html) statements.
3
3
 
4
4
  **Notice**: Still in its early stage, we built this for [BeanHub](https://beanhub.io)'s internal usage. May change rapidly. Use at your own risk for now.
5
5
 
@@ -9,7 +9,7 @@ BeanQueue, a lightweight worker queue framework based on [SQLAlchemy](https://ww
9
9
  - **Easy-to-deploy**: Only rely on PostgreSQL
10
10
  - **Easy-to-use**: Provide command line tools for processing tasks, also helpers for generating tasks models
11
11
  - **Auto-notify**: Notify will automatically be generated and send for inserted or update tasks
12
- - **Worker heartbeat and auto-reschedule**: Each worker keeps updating heartbeat, if one is dead, the others will reschedule the tasks
12
+ - **Worker heartbeat and auto-reschedule**: Each worker keeps updating heartbeat, if one is found dead, the others will reschedule the tasks
13
13
  - **Customizable**: Use it as an library and build your own worker queue
14
14
  - **Native DB operations**: Commit your tasks with other db entries altogether without worrying about data inconsistent issue
15
15
 
@@ -26,14 +26,15 @@ You can define a task processor like this
26
26
  ```python
27
27
  from sqlalchemy.orm import Session
28
28
 
29
- from bq.processors.registry import processor
30
- from bq import models
31
- from .. import my_models
29
+ import bq
30
+ from .. import models
32
31
  from .. import image_utils
33
32
 
34
- @processor(channel="images")
35
- def resize_image(db: Session, task: models.Task, width: int, height: int):
36
- image = db.query(my_models.Image).filter(my_models.Image.task == task).one()
33
+ app = bq.BeanQueue()
34
+
35
+ @app.processor(channel="images")
36
+ def resize_image(db: Session, task: bq.Task, width: int, height: int):
37
+ image = db.query(models.Image).filter(models.Image.task == task).one()
37
38
  image_utils.resize(image, size=(width, height))
38
39
  db.add(image)
39
40
  # by default the `processor` decorator has `auto_complete` flag turns on,
@@ -42,22 +43,23 @@ def resize_image(db: Session, task: models.Task, width: int, height: int):
42
43
 
43
44
  The `db` and `task` keyword arguments are optional.
44
45
  If you don't need to access the task object, you can simply define the function without these two parameters.
46
+ We also provide an optional `savepoint` argument in case if you want to rollback database changes you made.
45
47
 
46
- To submit a task, you can either use `bq.models.Task` model object to construct the task object, insert into the
48
+ To submit a task, you can either use `bq.Task` model object to construct the task object, insert into the
47
49
  database session and commit.
48
50
 
49
51
  ```python
50
- from bq import models
52
+ import bq
51
53
  from .db import Session
52
- from .. import my_models
54
+ from .. import models
53
55
 
54
56
  db = Session()
55
- task = models.Task(
57
+ task = bq.Task(
56
58
  channel="files",
57
59
  module="my_pkgs.files.processors",
58
60
  name="upload_to_s3_for_backup",
59
61
  )
60
- file = my_models.File(
62
+ file = models.File(
61
63
  task=task,
62
64
  blob_name="...",
63
65
  )
@@ -92,6 +94,7 @@ To run the worker, you can do this:
92
94
  BQ_PROCESSOR_PACKAGES='["my_pkgs.processors"]' python -m bq.cmds.process images
93
95
  ```
94
96
 
97
+ The `BQ_PROCESSOR_PACKAGES` is a JSON list contains the Python packages where you define your processors (the functions you decorated with `bq.processors.registry.processor`).
95
98
  To submit a task for testing purpose, you can do
96
99
 
97
100
  ```bash
@@ -110,29 +113,116 @@ Configurations can be modified by setting environment variables with `BQ_` prefi
110
113
  For example, to set the python packages to scan for processors, you can set `BQ_PROCESSOR_PACKAGES`.
111
114
  To change the PostgreSQL database to connect to, you can set `BQ_DATABASE_URL`.
112
115
  The complete definition of configurations can be found at the [bq/config.py](bq/config.py) module.
113
- For now, the configurations only affect command line tools.
114
116
 
115
- If you want to configure BeanQueue programmatically for the command lines, you can override our [dependency-injector](https://python-dependency-injector.ets-labs.org/)'s container defined at [bq/container.py](bq/container.py) and call the command function manually.
117
+ If you want to configure BeanQueue programmatically, you can pass in `Config` object to the `bq.BeanQueue` object when creating.
116
118
  For example:
117
119
 
118
120
  ```python
119
- import bq.cmds.process
120
- from bq.container import Container
121
- from bq.config import Config
122
-
123
- container = Container()
124
- container.wire(modules=[bq.cmds.process])
125
- with container.config.override(
126
- Config(
127
- PROCESSOR_PACKAGES=["my_pkgs.processors"],
128
- DATABASE_URL="postgresql://...",
129
- BATCH_SIZE=10,
121
+ import bq
122
+ from .my_config import config
123
+
124
+ container = bq.Container()
125
+ container.wire(packages=[bq])
126
+ config = bq.Config(
127
+ PROCESSOR_PACKAGES=["my_pkgs.processors"],
128
+ DATABASE_URL=str(config.DATABASE_URL),
129
+ BATCH_SIZE=10,
130
+ )
131
+ app = bq.BeanQueue(config=config)
132
+ ```
133
+
134
+ Then you can pass `--app` argument pointing to the app object to the process command like this:
135
+
136
+ ```bash
137
+ python -m bq.cmds.process -a my_pkgs.bq.app images
138
+ ```
139
+
140
+ Or if you prefer to define your own process command, you can also call `process_tasks` of the `BeanQueue` object directly like this:
141
+
142
+ ```python
143
+ app.process_tasks(channels=("images",))
144
+ ```
145
+
146
+ ### Define your own tables
147
+
148
+ BeanQueue is designed to be as customizable as much as possible.
149
+ Of course, you can define your own SQLAlchemy model instead of using the ones we provided.
150
+
151
+ To make defining your own `Task` model or `Worker` model much easier, you can use our mixin classes:
152
+
153
+ - `bq.TaskModelMixin`: provides task model columns
154
+ - `bq.TaskModelRefWorkerMixin`: provides foreign key column and relationship to `bq.Worker`
155
+ - `bq.WorkerModelMixin`: provides worker model columns
156
+ - `bq.WorkerRefMixin`: provides relationship to `bq.Task`
157
+
158
+ Here's an example for defining your own Task model:
159
+
160
+ ```python
161
+ import uuid
162
+
163
+ import bq
164
+ from sqlalchemy import ForeignKey
165
+ from sqlalchemy.dialects.postgresql import UUID
166
+ from sqlalchemy.orm import Mapped
167
+ from sqlalchemy.orm import mapped_column
168
+ from sqlalchemy.orm import relationship
169
+
170
+ from .base_class import Base
171
+
172
+
173
+ class Task(bq.TaskModelMixin, Base):
174
+ __tablename__ = "task"
175
+ worker_id: Mapped[uuid.UUID] = mapped_column(
176
+ UUID(as_uuid=True),
177
+ ForeignKey("worker.id", onupdate="CASCADE"),
178
+ nullable=True,
179
+ index=True,
180
+ )
181
+
182
+ worker: Mapped["Worker"] = relationship(
183
+ "Worker", back_populates="tasks", uselist=False
130
184
  )
131
- ):
132
- bq.cmds.process.process_tasks(channels=("images",))
133
185
  ```
134
186
 
135
- Many other behaviors of this framework can also be modified by overriding the container defined at [bq/container.py](bq/container.py).
187
+ To make task insert and update with state changing to `PENDING` send out NOTIFY "channel" statement automatically, you can also use `bq.models.task.listen_events` helper to register our SQLAlchemy event handlers automatically like this
188
+
189
+ ```python
190
+ from bq.models.task import listen_events
191
+ listen_events(Task)
192
+ ```
193
+
194
+ You just see how easy it is to define your Task model. Now, here's an example for defining your own Worker model:
195
+
196
+ ```python
197
+ import bq
198
+ from sqlalchemy.orm import Mapped
199
+ from sqlalchemy.orm import relationship
200
+
201
+ from .base_class import Base
202
+
203
+
204
+ class Worker(bq.WorkerModelMixin, Base):
205
+ __tablename__ = "worker"
206
+
207
+ tasks: Mapped[list["Task"]] = relationship(
208
+ "Task",
209
+ back_populates="worker",
210
+ cascade="all,delete",
211
+ order_by="Task.created_at",
212
+ )
213
+ ```
214
+
215
+ With the model class ready, you only need to change the `TASK_MODEL` and `WORKER_MODEL` of `Config` to the full Python module name plus the class name like this.
216
+
217
+ ```python
218
+ import bq
219
+ config = bq.Config(
220
+ TASK_MODEL="my_pkgs.models.Task",
221
+ WORKER_MODEL="my_pkgs.models.Worker",
222
+ # ... other configs
223
+ )
224
+ app = bq.BeanQueue(config)
225
+ ```
136
226
 
137
227
  ## Why?
138
228
 
@@ -210,5 +300,6 @@ A modern accounting book service based on the most popular open source version c
210
300
 
211
301
  - [solid_queue](https://github.com/rails/solid_queue)
212
302
  - [postgres-tq](https://github.com/flix-tech/postgres-tq)
303
+ - [pq](https://github.com/malthe/pq/)
213
304
  - [PgQueuer](https://github.com/janbjorge/PgQueuer)
214
305
  - [hatchet](https://github.com/hatchet-dev/hatchet)
@@ -0,0 +1,10 @@
1
+ from .app import BeanQueue
2
+ from .config import Config # noqa
3
+ from .models import Task # noqa
4
+ from .models import TaskModelMixin
5
+ from .models import TaskModelRefWorkerMixin
6
+ from .models import TaskState # noqa
7
+ from .models import Worker # noqa
8
+ from .models import WorkerModelMixin # noqa
9
+ from .models import WorkerRefMixin # noqa
10
+ from .models import WorkerState # noqa