tina4-python 3.10.58__tar.gz → 3.10.60__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.
- {tina4_python-3.10.58 → tina4_python-3.10.60}/PKG-INFO +1 -1
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/HtmlElement.py +1 -1
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/Testing.py +1 -1
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/__init__.py +2 -2
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/cli/__init__.py +52 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/router.py +6 -6
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/server.py +79 -17
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/dev_admin/metrics.py +21 -2
- {tina4_python-3.10.58 → tina4_python-3.10.60}/.gitignore +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/README.md +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/pyproject.toml +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/events.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/request.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/core/response.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/dev_reload.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Tina4 Python v3.0 —
|
|
1
|
+
# Tina4 Python v3.0 — The Intelligent Native Application 4ramework.
|
|
2
2
|
# Copyright 2007 - present Tina4
|
|
3
3
|
# License: MIT https://opensource.org/licenses/MIT
|
|
4
4
|
"""
|
|
@@ -8,7 +8,7 @@ Tina4 Python v3.0 — Zero-dependency, lightweight web framework.
|
|
|
8
8
|
|
|
9
9
|
One import, everything works.
|
|
10
10
|
"""
|
|
11
|
-
__version__ = "3.10.
|
|
11
|
+
__version__ = "3.10.60"
|
|
12
12
|
|
|
13
13
|
# ── Route decorators ──
|
|
14
14
|
from tina4_python.core.router import ( # noqa: E402, F401
|
|
@@ -137,6 +137,7 @@ def main():
|
|
|
137
137
|
"build": _build,
|
|
138
138
|
"ai": _ai,
|
|
139
139
|
"generate": _generate,
|
|
140
|
+
"console": _console,
|
|
140
141
|
"help": _help,
|
|
141
142
|
}
|
|
142
143
|
|
|
@@ -166,6 +167,7 @@ Commands:
|
|
|
166
167
|
test Run test suite
|
|
167
168
|
build Build distributable package
|
|
168
169
|
ai [--all] Install AI coding assistant context
|
|
170
|
+
console Start interactive REPL with framework loaded
|
|
169
171
|
|
|
170
172
|
Generators:
|
|
171
173
|
generate model <Name> [--fields "name:string,price:float"]
|
|
@@ -185,6 +187,56 @@ https://tina4.com
|
|
|
185
187
|
""")
|
|
186
188
|
|
|
187
189
|
|
|
190
|
+
# ── Console ───────────────────────────────────────────────────────────
|
|
191
|
+
|
|
192
|
+
def _console(args=None):
|
|
193
|
+
"""Start an interactive REPL with the framework loaded."""
|
|
194
|
+
import code
|
|
195
|
+
import os
|
|
196
|
+
|
|
197
|
+
# Load environment
|
|
198
|
+
from tina4_python.dotenv import load_env
|
|
199
|
+
load_env()
|
|
200
|
+
|
|
201
|
+
# Import everything the user needs
|
|
202
|
+
from tina4_python import get, post, put, patch, delete, Router, Database, ORM, Auth, Queue, Frond
|
|
203
|
+
from tina4_python.debug import Log
|
|
204
|
+
from tina4_python.api import Api
|
|
205
|
+
from tina4_python.core.events import on, emit
|
|
206
|
+
|
|
207
|
+
# Try to connect database from DATABASE_URL
|
|
208
|
+
db = None
|
|
209
|
+
db_url = os.environ.get("DATABASE_URL")
|
|
210
|
+
if db_url:
|
|
211
|
+
try:
|
|
212
|
+
db = Database(db_url)
|
|
213
|
+
print(f" Database: {db_url}")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
print(f" Database: failed ({e})")
|
|
216
|
+
|
|
217
|
+
# Auto-discover routes
|
|
218
|
+
from tina4_python.core.server import _auto_discover
|
|
219
|
+
_auto_discover("src")
|
|
220
|
+
route_count = len(Router.get_routes())
|
|
221
|
+
print(f" Routes: {route_count} discovered")
|
|
222
|
+
|
|
223
|
+
banner = (
|
|
224
|
+
"\n Tina4 Python Console\n"
|
|
225
|
+
" Type Python code. Framework is loaded.\n"
|
|
226
|
+
" Available: db, Router, ORM, Database, Auth, Api, Log, Queue\n"
|
|
227
|
+
" Exit: Ctrl+D or exit()\n"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
local_vars = {
|
|
231
|
+
"db": db, "Database": Database, "ORM": ORM, "Router": Router,
|
|
232
|
+
"Auth": Auth, "Api": Api, "Log": Log, "Queue": Queue,
|
|
233
|
+
"Frond": Frond, "get": get, "post": post, "put": put,
|
|
234
|
+
"patch": patch, "delete": delete, "on": on, "emit": emit,
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
code.interact(banner=banner, local=local_vars)
|
|
238
|
+
|
|
239
|
+
|
|
188
240
|
# ── Init ──────────────────────────────────────────────────────────────
|
|
189
241
|
|
|
190
242
|
def _init(args):
|
|
@@ -79,22 +79,22 @@ class RouteGroup:
|
|
|
79
79
|
self._middleware = middleware or []
|
|
80
80
|
|
|
81
81
|
def get(self, path: str, handler, **options) -> RouteRef:
|
|
82
|
-
return self._router.add("GET",
|
|
82
|
+
return self._router.add("GET", path, handler, middleware=self._middleware, **options)
|
|
83
83
|
|
|
84
84
|
def post(self, path: str, handler, **options) -> RouteRef:
|
|
85
|
-
return self._router.add("POST",
|
|
85
|
+
return self._router.add("POST", path, handler, middleware=self._middleware, **options)
|
|
86
86
|
|
|
87
87
|
def put(self, path: str, handler, **options) -> RouteRef:
|
|
88
|
-
return self._router.add("PUT",
|
|
88
|
+
return self._router.add("PUT", path, handler, middleware=self._middleware, **options)
|
|
89
89
|
|
|
90
90
|
def patch(self, path: str, handler, **options) -> RouteRef:
|
|
91
|
-
return self._router.add("PATCH",
|
|
91
|
+
return self._router.add("PATCH", path, handler, middleware=self._middleware, **options)
|
|
92
92
|
|
|
93
93
|
def delete(self, path: str, handler, **options) -> RouteRef:
|
|
94
|
-
return self._router.add("DELETE",
|
|
94
|
+
return self._router.add("DELETE", path, handler, middleware=self._middleware, **options)
|
|
95
95
|
|
|
96
96
|
def any(self, path: str, handler, **options) -> RouteRef:
|
|
97
|
-
return self._router.add("ANY",
|
|
97
|
+
return self._router.add("ANY", path, handler, middleware=self._middleware, **options)
|
|
98
98
|
|
|
99
99
|
def group(self, prefix: str, callback, middleware=None):
|
|
100
100
|
merged = list(self._middleware) + (middleware or [])
|
|
@@ -254,7 +254,7 @@ h1{{font-size:3rem;font-weight:700;margin-bottom:0.25rem;letter-spacing:-1px}}
|
|
|
254
254
|
<div class="hero">
|
|
255
255
|
<img src="/images/tina4-logo-icon.webp" class="logo" alt="Tina4">
|
|
256
256
|
<h1>Tina4Python</h1>
|
|
257
|
-
<p class="tagline">
|
|
257
|
+
<p class="tagline">The Intelligent Native Application 4ramework</p>
|
|
258
258
|
<div class="actions">
|
|
259
259
|
<a href="https://tina4.com/python" class="btn" target="_blank">Website</a>
|
|
260
260
|
<a href="/__dev" class="btn">Dev Admin</a>
|
|
@@ -1175,19 +1175,84 @@ def _find_production_server():
|
|
|
1175
1175
|
return None
|
|
1176
1176
|
|
|
1177
1177
|
|
|
1178
|
+
def _kill_port(port: int) -> None:
|
|
1179
|
+
"""Kill whatever process is listening on *port*.
|
|
1180
|
+
|
|
1181
|
+
Uses lsof on macOS/Linux and netstat + taskkill on Windows.
|
|
1182
|
+
Raises RuntimeError if the port cannot be freed.
|
|
1183
|
+
"""
|
|
1184
|
+
import subprocess
|
|
1185
|
+
import time
|
|
1186
|
+
|
|
1187
|
+
print(f" Port {port} in use — killing existing process...")
|
|
1188
|
+
|
|
1189
|
+
if sys.platform == "win32":
|
|
1190
|
+
# Find PID via netstat
|
|
1191
|
+
try:
|
|
1192
|
+
result = subprocess.run(
|
|
1193
|
+
["netstat", "-ano"],
|
|
1194
|
+
capture_output=True, text=True, timeout=5
|
|
1195
|
+
)
|
|
1196
|
+
pid = None
|
|
1197
|
+
for line in result.stdout.splitlines():
|
|
1198
|
+
if f":{port}" in line and ("LISTENING" in line or "ESTABLISHED" in line):
|
|
1199
|
+
parts = line.split()
|
|
1200
|
+
if parts:
|
|
1201
|
+
pid = parts[-1]
|
|
1202
|
+
break
|
|
1203
|
+
if pid and pid.isdigit():
|
|
1204
|
+
subprocess.run(["taskkill", "/PID", pid, "/F"], timeout=5)
|
|
1205
|
+
except Exception as e:
|
|
1206
|
+
raise RuntimeError(f"Could not free port {port}: {e}") from e
|
|
1207
|
+
else:
|
|
1208
|
+
# macOS / Linux — use lsof
|
|
1209
|
+
try:
|
|
1210
|
+
result = subprocess.run(
|
|
1211
|
+
["lsof", "-ti", f":{port}"],
|
|
1212
|
+
capture_output=True, text=True, timeout=5
|
|
1213
|
+
)
|
|
1214
|
+
pids = result.stdout.strip().splitlines()
|
|
1215
|
+
if not pids:
|
|
1216
|
+
return # Nothing found — port may have freed itself
|
|
1217
|
+
for pid_str in pids:
|
|
1218
|
+
pid_str = pid_str.strip()
|
|
1219
|
+
if pid_str.isdigit():
|
|
1220
|
+
os.kill(int(pid_str), signal.SIGTERM)
|
|
1221
|
+
except FileNotFoundError:
|
|
1222
|
+
# lsof not available — try fuser
|
|
1223
|
+
try:
|
|
1224
|
+
result = subprocess.run(
|
|
1225
|
+
["fuser", f"{port}/tcp"],
|
|
1226
|
+
capture_output=True, text=True, timeout=5
|
|
1227
|
+
)
|
|
1228
|
+
for pid_str in result.stdout.split():
|
|
1229
|
+
if pid_str.isdigit():
|
|
1230
|
+
os.kill(int(pid_str), signal.SIGTERM)
|
|
1231
|
+
except Exception as e:
|
|
1232
|
+
raise RuntimeError(f"Could not free port {port}: {e}") from e
|
|
1233
|
+
except Exception as e:
|
|
1234
|
+
raise RuntimeError(f"Could not free port {port}: {e}") from e
|
|
1235
|
+
|
|
1236
|
+
# Give the OS a moment to reclaim the port
|
|
1237
|
+
time.sleep(0.5)
|
|
1238
|
+
print(f" Port {port} freed")
|
|
1239
|
+
|
|
1240
|
+
|
|
1178
1241
|
def _find_available_port(start: int, max_tries: int = 10) -> int:
|
|
1179
|
-
"""
|
|
1242
|
+
"""Check if *start* is available; if not, kill the process on it and return *start*.
|
|
1243
|
+
|
|
1244
|
+
The auto-increment behaviour is intentionally removed — the server always
|
|
1245
|
+
claims the requested port. If killing fails a RuntimeError is raised.
|
|
1246
|
+
"""
|
|
1180
1247
|
import socket
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
continue
|
|
1190
|
-
return start
|
|
1248
|
+
try:
|
|
1249
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
1250
|
+
s.bind(("127.0.0.1", start))
|
|
1251
|
+
s.close()
|
|
1252
|
+
return start
|
|
1253
|
+
except OSError:
|
|
1254
|
+
_kill_port(start)
|
|
1255
|
+
return start
|
|
1191
1256
|
|
|
1192
1257
|
|
|
1193
1258
|
def _open_browser(url: str):
|
|
@@ -1247,7 +1312,7 @@ def _print_banner(host: str, port: int, server_name: str = "asyncio", ai_port: i
|
|
|
1247
1312
|
/ / / / / / / /_/ /__ __/
|
|
1248
1313
|
/_/ /_/_/ /_/\\__,_/ /_/
|
|
1249
1314
|
{reset}
|
|
1250
|
-
Tina4 Python v{__version__} —
|
|
1315
|
+
Tina4 Python v{__version__} — The Intelligent Native Application 4ramework
|
|
1251
1316
|
|
|
1252
1317
|
Server: http://{display}:{port} ({server_name})
|
|
1253
1318
|
Swagger: http://localhost:{port}/swagger
|
|
@@ -1300,11 +1365,8 @@ def run(host: str | None = None, port: int | None = None, no_browser: bool = Fal
|
|
|
1300
1365
|
# Resolve host/port (CLI arg > ENV > default)
|
|
1301
1366
|
host, port = resolve_config(cli_host=host, cli_port=port)
|
|
1302
1367
|
|
|
1303
|
-
#
|
|
1304
|
-
original_port = port
|
|
1368
|
+
# Claim the requested port — kill whatever is on it if needed
|
|
1305
1369
|
port = _find_available_port(port)
|
|
1306
|
-
if port != original_port:
|
|
1307
|
-
Log.info(f"Port {original_port} in use — using {port} instead")
|
|
1308
1370
|
|
|
1309
1371
|
# Detect production server (unless TINA4_DEBUG is true)
|
|
1310
1372
|
from tina4_python.dotenv import is_truthy
|
|
@@ -495,7 +495,8 @@ def _count_halstead(node: ast.AST, stats: dict):
|
|
|
495
495
|
def _has_matching_test(rel_path: str) -> bool:
|
|
496
496
|
"""Check if a source file has a matching test file.
|
|
497
497
|
|
|
498
|
-
Looks for
|
|
498
|
+
Looks for common test file patterns and also scans any test file
|
|
499
|
+
that imports the module by name.
|
|
499
500
|
"""
|
|
500
501
|
p = Path(rel_path)
|
|
501
502
|
module = p.stem # e.g. "auth" from "tina4_python/auth/__init__.py"
|
|
@@ -506,8 +507,26 @@ def _has_matching_test(rel_path: str) -> bool:
|
|
|
506
507
|
Path("tests") / f"test_{module}.py",
|
|
507
508
|
Path("tests") / f"test_{module}s.py",
|
|
508
509
|
Path("test") / f"test_{module}.py",
|
|
510
|
+
Path("spec") / f"test_{module}.py",
|
|
511
|
+
Path("tests") / f"{module}_test.py",
|
|
509
512
|
]
|
|
510
|
-
|
|
513
|
+
if any(tp.exists() for tp in test_patterns):
|
|
514
|
+
return True
|
|
515
|
+
# Grep-based: check if any test file imports this module
|
|
516
|
+
import re
|
|
517
|
+
import_patterns = [re.compile(rf'\bimport\s+{re.escape(module)}\b'),
|
|
518
|
+
re.compile(rf'\bfrom\s+{re.escape(module)}\b')]
|
|
519
|
+
for test_dir in (Path("tests"), Path("test"), Path("spec")):
|
|
520
|
+
if not test_dir.is_dir():
|
|
521
|
+
continue
|
|
522
|
+
for test_file in test_dir.glob("*.py"):
|
|
523
|
+
try:
|
|
524
|
+
content = test_file.read_text(encoding="utf-8", errors="ignore")
|
|
525
|
+
if any(pat.search(content) for pat in import_patterns):
|
|
526
|
+
return True
|
|
527
|
+
except OSError:
|
|
528
|
+
pass
|
|
529
|
+
return False
|
|
511
530
|
|
|
512
531
|
|
|
513
532
|
def _maintainability_index(halstead_volume: float, avg_cc: float, loc: int) -> float:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/images/tina4-logo-icon.webp
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/public/swagger/oauth2-redirect.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/queue_backends/rabbitmq_backend.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/templates/docker/python/Dockerfile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.10.58 → tina4_python-3.10.60}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|