overflask 0.1.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.
- overflask-0.1.0/LICENSE +21 -0
- overflask-0.1.0/PKG-INFO +220 -0
- overflask-0.1.0/README.md +159 -0
- overflask-0.1.0/pyproject.toml +107 -0
- overflask-0.1.0/setup.cfg +4 -0
- overflask-0.1.0/src/overflask/__init__.py +23 -0
- overflask-0.1.0/src/overflask/analytics/__init__.py +1 -0
- overflask-0.1.0/src/overflask/analytics/analytics.py +72 -0
- overflask-0.1.0/src/overflask/analytics/commands.py +30 -0
- overflask-0.1.0/src/overflask/analytics/es_index_templates.py +51 -0
- overflask-0.1.0/src/overflask/analytics/events.py +43 -0
- overflask-0.1.0/src/overflask/analytics/flusher.py +75 -0
- overflask-0.1.0/src/overflask/analytics/purge.py +49 -0
- overflask-0.1.0/src/overflask/cli/__init__.py +0 -0
- overflask-0.1.0/src/overflask/cli/cli.py +269 -0
- overflask-0.1.0/src/overflask/commands/__init__.py +0 -0
- overflask-0.1.0/src/overflask/commands/commands.py +194 -0
- overflask-0.1.0/src/overflask/core/__init__.py +0 -0
- overflask-0.1.0/src/overflask/core/app.py +28 -0
- overflask-0.1.0/src/overflask/core/config.py +93 -0
- overflask-0.1.0/src/overflask/core/discovery.py +74 -0
- overflask-0.1.0/src/overflask/db/__init__.py +0 -0
- overflask-0.1.0/src/overflask/db/model_base.py +124 -0
- overflask-0.1.0/src/overflask/deploy/__init__.py +0 -0
- overflask-0.1.0/src/overflask/deploy/gunicorn.py +33 -0
- overflask-0.1.0/src/overflask/es/__init__.py +0 -0
- overflask-0.1.0/src/overflask/es/elastic.py +21 -0
- overflask-0.1.0/src/overflask/redis/__init__.py +0 -0
- overflask-0.1.0/src/overflask/redis/cache.py +173 -0
- overflask-0.1.0/src/overflask/redis/redis.py +22 -0
- overflask-0.1.0/src/overflask/tasks/__init__.py +0 -0
- overflask-0.1.0/src/overflask/tasks/callable_data.py +14 -0
- overflask-0.1.0/src/overflask/tasks/callable_registry.py +9 -0
- overflask-0.1.0/src/overflask/tasks/callable_wrapper.py +67 -0
- overflask-0.1.0/src/overflask/tasks/commands.py +37 -0
- overflask-0.1.0/src/overflask/tasks/es_index_templates.py +96 -0
- overflask-0.1.0/src/overflask/tasks/kibana.py +286 -0
- overflask-0.1.0/src/overflask/tasks/metrics.py +71 -0
- overflask-0.1.0/src/overflask/tasks/scheduler.py +101 -0
- overflask-0.1.0/src/overflask/tasks/task_store.py +214 -0
- overflask-0.1.0/src/overflask/tasks/tasks.py +46 -0
- overflask-0.1.0/src/overflask/tasks/validator.py +109 -0
- overflask-0.1.0/src/overflask/tasks/worker.py +61 -0
- overflask-0.1.0/src/overflask/templates/README.md.jinja +65 -0
- overflask-0.1.0/src/overflask/templates/component/__init__.py.jinja +1 -0
- overflask-0.1.0/src/overflask/templates/component/cli.py.jinja +14 -0
- overflask-0.1.0/src/overflask/templates/component/models.py.jinja +15 -0
- overflask-0.1.0/src/overflask/templates/component/services.py.jinja +4 -0
- overflask-0.1.0/src/overflask/templates/component/tasks.py.jinja +15 -0
- overflask-0.1.0/src/overflask/templates/component/tests.py.jinja +19 -0
- overflask-0.1.0/src/overflask/templates/component/views.py.jinja +16 -0
- overflask-0.1.0/src/overflask/templates/compose.yaml.jinja +94 -0
- overflask-0.1.0/src/overflask/templates/conftest.py.jinja +262 -0
- overflask-0.1.0/src/overflask/templates/dockerignore +29 -0
- overflask-0.1.0/src/overflask/templates/docs/README-overflask.md +582 -0
- overflask-0.1.0/src/overflask/templates/docs/gunicorn.md +87 -0
- overflask-0.1.0/src/overflask/templates/docs/traefik.md +81 -0
- overflask-0.1.0/src/overflask/templates/gitignore +33 -0
- overflask-0.1.0/src/overflask/templates/manage.py.jinja +85 -0
- overflask-0.1.0/src/overflask/templates/ops/env.template +47 -0
- overflask-0.1.0/src/overflask/templates/ops/migrations/alembic.ini.jinja +40 -0
- overflask-0.1.0/src/overflask/templates/ops/migrations/env.py.jinja +68 -0
- overflask-0.1.0/src/overflask/templates/ops/migrations/script.py.mako +24 -0
- overflask-0.1.0/src/overflask/templates/ops/setup-traefik.sh +338 -0
- overflask-0.1.0/src/overflask/templates/pytest.ini.jinja +9 -0
- overflask-0.1.0/src/overflask/templates/requirements.pip.jinja +15 -0
- overflask-0.1.0/src/overflask/templates/settings.py.jinja +92 -0
- overflask-0.1.0/src/overflask/testing/__init__.py +0 -0
- overflask-0.1.0/src/overflask/testing/testing.py +115 -0
- overflask-0.1.0/src/overflask.egg-info/PKG-INFO +220 -0
- overflask-0.1.0/src/overflask.egg-info/SOURCES.txt +77 -0
- overflask-0.1.0/src/overflask.egg-info/dependency_links.txt +1 -0
- overflask-0.1.0/src/overflask.egg-info/entry_points.txt +2 -0
- overflask-0.1.0/src/overflask.egg-info/requires.txt +14 -0
- overflask-0.1.0/src/overflask.egg-info/top_level.txt +1 -0
- overflask-0.1.0/tests/test_cli.py +1048 -0
- overflask-0.1.0/tests/test_model_base.py +210 -0
- overflask-0.1.0/tests/test_tasks.py +817 -0
- overflask-0.1.0/tests/test_testing.py +223 -0
overflask-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rklaus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
overflask-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: overflask
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Flask scaffolding CLI and runtime library — component architecture, SQLAlchemy, Redis caching, async tasks, and Elasticsearch analytics
|
|
5
|
+
Author: rklaus
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 rklaus
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
Project-URL: Homepage, https://gitlab.com/overflask/overflask
|
|
28
|
+
Project-URL: Source, https://gitlab.com/overflask/overflask
|
|
29
|
+
Project-URL: Tracker, https://gitlab.com/overflask/overflask/-/issues
|
|
30
|
+
Keywords: flask,scaffolding,cli,sqlalchemy,redis,elasticsearch,tasks,analytics
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Environment :: Web Environment
|
|
33
|
+
Classifier: Framework :: Flask
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
42
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
43
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
44
|
+
Requires-Python: >=3.10
|
|
45
|
+
Description-Content-Type: text/markdown
|
|
46
|
+
License-File: LICENSE
|
|
47
|
+
Requires-Dist: click>=8.0
|
|
48
|
+
Requires-Dist: flask>=3.0
|
|
49
|
+
Requires-Dist: python-dotenv>=1.0
|
|
50
|
+
Requires-Dist: sqlalchemy>=2.0
|
|
51
|
+
Requires-Dist: redis>=5.0
|
|
52
|
+
Requires-Dist: elasticsearch>=8.0
|
|
53
|
+
Requires-Dist: croniter>=3.0
|
|
54
|
+
Provides-Extra: dev
|
|
55
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
56
|
+
Requires-Dist: pytest-tmp-files>=0.0.2; extra == "dev"
|
|
57
|
+
Requires-Dist: ruff>=0.8; extra == "dev"
|
|
58
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
59
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
# overflask
|
|
63
|
+
|
|
64
|
+
A CLI tool that scaffolds Flask projects with component-based architecture, and a runtime library that adds SQLAlchemy models, Redis caching, async/recurring tasks, and Elasticsearch analytics on top of Flask.
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install overflask @ git+https://gitlab.com/hamdarsi/overflask.git
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Create a project
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
overflask create myapp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The CLI prompts for an initial component name and Postgres/Redis connection details, generates the project, pre-fills `.env`, and optionally starts the database containers.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
myapp/
|
|
82
|
+
├── manage.py # project CLI
|
|
83
|
+
├── settings.py # configuration
|
|
84
|
+
├── requirements.pip # dependencies
|
|
85
|
+
├── compose.yaml # Docker Compose
|
|
86
|
+
├── conftest.py # pytest fixtures
|
|
87
|
+
├── ops/
|
|
88
|
+
│ ├── env.template
|
|
89
|
+
│ ├── migrations/ # Alembic
|
|
90
|
+
│ └── setup-traefik.sh
|
|
91
|
+
├── docs/
|
|
92
|
+
└── components/
|
|
93
|
+
└── myapp/
|
|
94
|
+
├── models.py
|
|
95
|
+
├── views.py
|
|
96
|
+
├── services.py
|
|
97
|
+
├── cli.py
|
|
98
|
+
├── tasks.py
|
|
99
|
+
└── tests.py
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Add a component
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
python manage.py component add auth
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Creates `components/auth/` with the same structure and registers it in `settings.COMPONENTS`.
|
|
109
|
+
|
|
110
|
+
## Runtime library
|
|
111
|
+
|
|
112
|
+
Generated projects import directly from `overflask`:
|
|
113
|
+
|
|
114
|
+
### Models
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from overflask import ModelBase
|
|
118
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
119
|
+
|
|
120
|
+
class User(ModelBase):
|
|
121
|
+
# __tablename__ auto-set to "auth_users"
|
|
122
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
123
|
+
email: Mapped[str] = mapped_column(String(255), unique=True)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Table names are derived automatically as `{component}_{plural_snake_case_model_name}`.
|
|
127
|
+
|
|
128
|
+
### Redis cache
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from overflask import cached
|
|
132
|
+
|
|
133
|
+
@cached(ttl=300)
|
|
134
|
+
def get_user(user_id: int):
|
|
135
|
+
...
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Backed by Redis db 15 with stampede prevention via locking.
|
|
139
|
+
|
|
140
|
+
### Async tasks
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from overflask import async_task
|
|
144
|
+
|
|
145
|
+
@async_task
|
|
146
|
+
def send_welcome_email(user_id: int, email: str) -> None:
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
# Enqueue immediately or with a delay
|
|
150
|
+
send_welcome_email.queue(user_id=42, email="alice@example.com")
|
|
151
|
+
send_welcome_email.queue(timedelta(minutes=5), user_id=42, email="alice@example.com")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Recurring tasks
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from overflask import recurring_task
|
|
158
|
+
|
|
159
|
+
@recurring_task("0 9 * * MON-FRI")
|
|
160
|
+
def daily_report() -> None:
|
|
161
|
+
...
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Tasks are stored in Elasticsearch and dispatched via Redis. Run the supporting processes:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
python manage.py tasks scheduler # polls ES, dispatches due tasks
|
|
168
|
+
python manage.py tasks worker # executes tasks from Redis queue
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Analytics
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
from overflask.analytics import Analytics
|
|
175
|
+
|
|
176
|
+
Analytics.record("user.registered", area="auth", plan="free")
|
|
177
|
+
Analytics.record("purchase.completed", area="billing", amount=99)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Events are buffered in-process and bulk-flushed to Elasticsearch in the background. Each `area` gets its own monthly index (`myapp_analytics_auth-2026.03`).
|
|
181
|
+
|
|
182
|
+
## Database migrations
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
python manage.py db migrate -m "add users table"
|
|
186
|
+
python manage.py db upgrade
|
|
187
|
+
python manage.py db downgrade 001
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
pytest # all tests
|
|
194
|
+
pytest -m unit # unit tests only (no DB)
|
|
195
|
+
pytest -m integration
|
|
196
|
+
pytest -n auto # parallel
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Integration tests run against a real PostgreSQL instance using a cloned template database — fast isolation without `create_all()` per test. Redis is always replaced with fakeredis.
|
|
200
|
+
|
|
201
|
+
## Deployment
|
|
202
|
+
|
|
203
|
+
The generated project ships with a Gunicorn config and Traefik integration for HTTPS:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
bash ops/setup-traefik.sh # one-time Traefik setup per server
|
|
207
|
+
docker compose up -d
|
|
208
|
+
docker compose exec web python manage.py db upgrade
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
See `docs/traefik.md` and `docs/gunicorn.md` in the generated project for details.
|
|
212
|
+
|
|
213
|
+
## Development
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
pip install -e ".[dev]"
|
|
217
|
+
pytest
|
|
218
|
+
ruff check src/ tests/
|
|
219
|
+
ruff format src/ tests/
|
|
220
|
+
```
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# overflask
|
|
2
|
+
|
|
3
|
+
A CLI tool that scaffolds Flask projects with component-based architecture, and a runtime library that adds SQLAlchemy models, Redis caching, async/recurring tasks, and Elasticsearch analytics on top of Flask.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install overflask @ git+https://gitlab.com/hamdarsi/overflask.git
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Create a project
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
overflask create myapp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The CLI prompts for an initial component name and Postgres/Redis connection details, generates the project, pre-fills `.env`, and optionally starts the database containers.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
myapp/
|
|
21
|
+
├── manage.py # project CLI
|
|
22
|
+
├── settings.py # configuration
|
|
23
|
+
├── requirements.pip # dependencies
|
|
24
|
+
├── compose.yaml # Docker Compose
|
|
25
|
+
├── conftest.py # pytest fixtures
|
|
26
|
+
├── ops/
|
|
27
|
+
│ ├── env.template
|
|
28
|
+
│ ├── migrations/ # Alembic
|
|
29
|
+
│ └── setup-traefik.sh
|
|
30
|
+
├── docs/
|
|
31
|
+
└── components/
|
|
32
|
+
└── myapp/
|
|
33
|
+
├── models.py
|
|
34
|
+
├── views.py
|
|
35
|
+
├── services.py
|
|
36
|
+
├── cli.py
|
|
37
|
+
├── tasks.py
|
|
38
|
+
└── tests.py
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Add a component
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python manage.py component add auth
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Creates `components/auth/` with the same structure and registers it in `settings.COMPONENTS`.
|
|
48
|
+
|
|
49
|
+
## Runtime library
|
|
50
|
+
|
|
51
|
+
Generated projects import directly from `overflask`:
|
|
52
|
+
|
|
53
|
+
### Models
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from overflask import ModelBase
|
|
57
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
58
|
+
|
|
59
|
+
class User(ModelBase):
|
|
60
|
+
# __tablename__ auto-set to "auth_users"
|
|
61
|
+
id: Mapped[int] = mapped_column(primary_key=True)
|
|
62
|
+
email: Mapped[str] = mapped_column(String(255), unique=True)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Table names are derived automatically as `{component}_{plural_snake_case_model_name}`.
|
|
66
|
+
|
|
67
|
+
### Redis cache
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from overflask import cached
|
|
71
|
+
|
|
72
|
+
@cached(ttl=300)
|
|
73
|
+
def get_user(user_id: int):
|
|
74
|
+
...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Backed by Redis db 15 with stampede prevention via locking.
|
|
78
|
+
|
|
79
|
+
### Async tasks
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from overflask import async_task
|
|
83
|
+
|
|
84
|
+
@async_task
|
|
85
|
+
def send_welcome_email(user_id: int, email: str) -> None:
|
|
86
|
+
...
|
|
87
|
+
|
|
88
|
+
# Enqueue immediately or with a delay
|
|
89
|
+
send_welcome_email.queue(user_id=42, email="alice@example.com")
|
|
90
|
+
send_welcome_email.queue(timedelta(minutes=5), user_id=42, email="alice@example.com")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Recurring tasks
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from overflask import recurring_task
|
|
97
|
+
|
|
98
|
+
@recurring_task("0 9 * * MON-FRI")
|
|
99
|
+
def daily_report() -> None:
|
|
100
|
+
...
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Tasks are stored in Elasticsearch and dispatched via Redis. Run the supporting processes:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python manage.py tasks scheduler # polls ES, dispatches due tasks
|
|
107
|
+
python manage.py tasks worker # executes tasks from Redis queue
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Analytics
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from overflask.analytics import Analytics
|
|
114
|
+
|
|
115
|
+
Analytics.record("user.registered", area="auth", plan="free")
|
|
116
|
+
Analytics.record("purchase.completed", area="billing", amount=99)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Events are buffered in-process and bulk-flushed to Elasticsearch in the background. Each `area` gets its own monthly index (`myapp_analytics_auth-2026.03`).
|
|
120
|
+
|
|
121
|
+
## Database migrations
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
python manage.py db migrate -m "add users table"
|
|
125
|
+
python manage.py db upgrade
|
|
126
|
+
python manage.py db downgrade 001
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Testing
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pytest # all tests
|
|
133
|
+
pytest -m unit # unit tests only (no DB)
|
|
134
|
+
pytest -m integration
|
|
135
|
+
pytest -n auto # parallel
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Integration tests run against a real PostgreSQL instance using a cloned template database — fast isolation without `create_all()` per test. Redis is always replaced with fakeredis.
|
|
139
|
+
|
|
140
|
+
## Deployment
|
|
141
|
+
|
|
142
|
+
The generated project ships with a Gunicorn config and Traefik integration for HTTPS:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
bash ops/setup-traefik.sh # one-time Traefik setup per server
|
|
146
|
+
docker compose up -d
|
|
147
|
+
docker compose exec web python manage.py db upgrade
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
See `docs/traefik.md` and `docs/gunicorn.md` in the generated project for details.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pip install -e ".[dev]"
|
|
156
|
+
pytest
|
|
157
|
+
ruff check src/ tests/
|
|
158
|
+
ruff format src/ tests/
|
|
159
|
+
```
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "overflask"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Flask scaffolding CLI and runtime library — component architecture, SQLAlchemy, Redis caching, async tasks, and Elasticsearch analytics"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
authors = [{ name = "rklaus" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["flask", "scaffolding", "cli", "sqlalchemy", "redis", "elasticsearch", "tasks", "analytics"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Web Environment",
|
|
17
|
+
"Framework :: Flask",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.10",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
|
26
|
+
"Topic :: Software Development :: Code Generators",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
dependencies = [
|
|
31
|
+
"click>=8.0",
|
|
32
|
+
"flask>=3.0",
|
|
33
|
+
"python-dotenv>=1.0",
|
|
34
|
+
"sqlalchemy>=2.0",
|
|
35
|
+
"redis>=5.0",
|
|
36
|
+
"elasticsearch>=8.0",
|
|
37
|
+
"croniter>=3.0",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://gitlab.com/overflask/overflask"
|
|
42
|
+
Source = "https://gitlab.com/overflask/overflask"
|
|
43
|
+
Tracker = "https://gitlab.com/overflask/overflask/-/issues"
|
|
44
|
+
|
|
45
|
+
[project.optional-dependencies]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=7.0",
|
|
48
|
+
"pytest-tmp-files>=0.0.2",
|
|
49
|
+
"ruff>=0.8",
|
|
50
|
+
"build>=1.0",
|
|
51
|
+
"twine>=5.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.scripts]
|
|
55
|
+
overflask = "overflask.cli.cli:main"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
where = ["src"]
|
|
59
|
+
|
|
60
|
+
[tool.setuptools.package-data]
|
|
61
|
+
overflask = ["templates/**/*"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
65
|
+
markers = [
|
|
66
|
+
"fixture_file: load JSON fixture files into db_session",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[tool.pyright]
|
|
70
|
+
extraPaths = ["src"]
|
|
71
|
+
exclude = ["build", "dist", "*.egg-info"]
|
|
72
|
+
|
|
73
|
+
[tool.ruff]
|
|
74
|
+
line-length = 120
|
|
75
|
+
target-version = "py310"
|
|
76
|
+
|
|
77
|
+
[tool.ruff.lint]
|
|
78
|
+
select = [
|
|
79
|
+
"E", # pycodestyle errors
|
|
80
|
+
"W", # pycodestyle warnings
|
|
81
|
+
"F", # pyflakes
|
|
82
|
+
"I", # isort (import sorting)
|
|
83
|
+
"N", # pep8-naming
|
|
84
|
+
"UP", # pyupgrade
|
|
85
|
+
"B", # flake8-bugbear
|
|
86
|
+
"C4", # flake8-comprehensions
|
|
87
|
+
"SIM", # flake8-simplify
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
ignore = [
|
|
91
|
+
"TC001", # Move application imports to TYPE_CHECKING
|
|
92
|
+
"TC003", # Move standard library imports to TYPE_CHECKING
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[tool.ruff.lint.per-file-ignores]
|
|
96
|
+
"tests/**/*.py" = [
|
|
97
|
+
"N806", # Non-lowercase variables
|
|
98
|
+
"F841", # Unused variables (test fixtures)
|
|
99
|
+
"SIM117", # Nested with statements
|
|
100
|
+
"B023", # Function uses loop variable
|
|
101
|
+
"F401", # Unused imports
|
|
102
|
+
"E402", # Module import not at top
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
[tool.ruff.format]
|
|
106
|
+
quote-style = "single"
|
|
107
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""overflask - A CLI tool to scaffold Flask projects with component-based architecture."""
|
|
2
|
+
|
|
3
|
+
from overflask.analytics.analytics import Analytics
|
|
4
|
+
from overflask.core.app import OverFlask
|
|
5
|
+
from overflask.core.config import Config
|
|
6
|
+
from overflask.db.model_base import ModelBase
|
|
7
|
+
from overflask.redis.cache import CachePopulationTimeoutError, cached
|
|
8
|
+
from overflask.redis.redis import get_redis
|
|
9
|
+
from overflask.tasks.tasks import async_task, delete, recurring_task
|
|
10
|
+
|
|
11
|
+
__version__ = '0.1.0'
|
|
12
|
+
__all__ = [
|
|
13
|
+
'Analytics',
|
|
14
|
+
'CachePopulationTimeoutError',
|
|
15
|
+
'Config',
|
|
16
|
+
'ModelBase',
|
|
17
|
+
'OverFlask',
|
|
18
|
+
'async_task',
|
|
19
|
+
'cached',
|
|
20
|
+
'delete',
|
|
21
|
+
'get_redis',
|
|
22
|
+
'recurring_task',
|
|
23
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Analytics package for overflask."""
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Analytics static class for recording events."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from flask import g, request
|
|
8
|
+
|
|
9
|
+
from overflask.analytics.events import AnalyticsEvents
|
|
10
|
+
from overflask.analytics.flusher import AnalyticsFlusher
|
|
11
|
+
from overflask.core.config import Config
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _normalize_area(area: str) -> str:
|
|
15
|
+
"""Lowercase and replace non-alphanumeric characters with underscores."""
|
|
16
|
+
return re.sub(r"[^a-z0-9]+", "_", area.lower()).strip("_")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Analytics:
|
|
20
|
+
"""Record analytics events. Thread-safe; buffered in-process."""
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def record(event: str, *, area: str, **data: Any) -> None:
|
|
24
|
+
"""Record an analytics event.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
event: Event name (e.g. "user.registered").
|
|
28
|
+
area: Required domain/component label (e.g. "auth"). Determines the ES index.
|
|
29
|
+
**data: Arbitrary key-value pairs stored under ``data`` in the event envelope.
|
|
30
|
+
"""
|
|
31
|
+
doc = Analytics._build_doc(event, area, data)
|
|
32
|
+
AnalyticsEvents.queue(doc)
|
|
33
|
+
AnalyticsFlusher.ensure()
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def _build_doc(event: str, area: str, data: dict) -> dict:
|
|
37
|
+
"""Build the full event envelope with optional request context enrichment."""
|
|
38
|
+
doc: dict[str, Any] = {
|
|
39
|
+
"event": event,
|
|
40
|
+
"timestamp": datetime.now(tz=timezone.utc).isoformat(),
|
|
41
|
+
"project": Config.project_name(),
|
|
42
|
+
"environment": Config.analytics_environment(),
|
|
43
|
+
"area": _normalize_area(area),
|
|
44
|
+
"data": data,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Auto-enrich with Flask request context if available.
|
|
48
|
+
try:
|
|
49
|
+
ctx: dict[str, str] = {
|
|
50
|
+
"http_method": request.method,
|
|
51
|
+
"http_path": request.path,
|
|
52
|
+
}
|
|
53
|
+
request_id = getattr(g, "request_id", None) or request.headers.get("X-Request-ID")
|
|
54
|
+
if request_id:
|
|
55
|
+
ctx["request_id"] = request_id
|
|
56
|
+
user_id = getattr(g, "user_id", None)
|
|
57
|
+
if user_id is not None:
|
|
58
|
+
ctx["user_id"] = str(user_id)
|
|
59
|
+
doc["context"] = ctx
|
|
60
|
+
except RuntimeError:
|
|
61
|
+
# No Flask request context — omit context entirely.
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
return doc
|
|
65
|
+
|
|
66
|
+
@staticmethod
|
|
67
|
+
def flush_now() -> int:
|
|
68
|
+
"""Drain the buffer and bulk-write to ES immediately.
|
|
69
|
+
|
|
70
|
+
Returns the number of events flushed.
|
|
71
|
+
"""
|
|
72
|
+
return AnalyticsFlusher.flush_now()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Click commands for the analytics system."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from flask import Flask
|
|
5
|
+
|
|
6
|
+
from overflask.analytics.es_index_templates import AnalyticsEsIndexTemplates
|
|
7
|
+
from overflask.analytics.purge import AnalyticsPurge
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
def analytics():
|
|
12
|
+
"""Analytics management commands."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@analytics.command("provision")
|
|
16
|
+
@click.pass_obj
|
|
17
|
+
def provision_cmd(app: Flask) -> None:
|
|
18
|
+
"""Create or update the Elasticsearch index template for analytics indices."""
|
|
19
|
+
with app.app_context():
|
|
20
|
+
AnalyticsEsIndexTemplates.ensure()
|
|
21
|
+
click.echo("Analytics ES index template provisioned.")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@analytics.command("purge")
|
|
25
|
+
@click.pass_obj
|
|
26
|
+
def purge_cmd(app: Flask) -> None:
|
|
27
|
+
"""Delete analytics indices older than the configured retention period."""
|
|
28
|
+
with app.app_context():
|
|
29
|
+
AnalyticsPurge.purge_old()
|
|
30
|
+
click.echo("Analytics purge complete.")
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Elasticsearch index templates and naming helpers for the analytics system."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
from overflask.core.config import Config
|
|
6
|
+
from overflask.es.elastic import get_es_client
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AnalyticsEsIndexTemplates:
|
|
10
|
+
"""Manages ES index templates and index naming for the analytics system."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def analytics_index(project: str, area: str, dt: datetime) -> str:
|
|
14
|
+
"""Return the monthly analytics index name for the given project, area, and datetime."""
|
|
15
|
+
return f"{project}_analytics_{area}-{dt:%Y.%m}"
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def ensure() -> None:
|
|
19
|
+
"""Create or update the ES index template for all analytics index patterns.
|
|
20
|
+
|
|
21
|
+
Idempotent — safe to call on every scheduler and worker startup.
|
|
22
|
+
"""
|
|
23
|
+
es = get_es_client()
|
|
24
|
+
project = Config.project_name()
|
|
25
|
+
|
|
26
|
+
properties = {
|
|
27
|
+
"event": {"type": "keyword"},
|
|
28
|
+
"timestamp": {"type": "date"},
|
|
29
|
+
"project": {"type": "keyword"},
|
|
30
|
+
"environment": {"type": "keyword"},
|
|
31
|
+
"area": {"type": "keyword"},
|
|
32
|
+
"context": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"request_id": {"type": "keyword"},
|
|
36
|
+
"http_method": {"type": "keyword"},
|
|
37
|
+
"http_path": {"type": "keyword"},
|
|
38
|
+
"user_id": {"type": "keyword"},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
"data": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"dynamic": True,
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
es.indices.put_index_template(
|
|
48
|
+
name=f"{project}-analytics",
|
|
49
|
+
index_patterns=[f"{project}_analytics_*-*"],
|
|
50
|
+
template={"mappings": {"properties": properties}},
|
|
51
|
+
)
|