tina4-python 3.12.13__tar.gz → 3.12.14__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.12.13 → tina4_python-3.12.14}/PKG-INFO +1 -1
- {tina4_python-3.12.13 → tina4_python-3.12.14}/pyproject.toml +1 -1
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/__init__.py +1 -1
- tina4_python-3.12.14/tina4_python/test/__init__.py +389 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/.gitignore +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/README.md +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/CLAUDE.md +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/HtmlElement.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/Testing.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/ai/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/api/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/auth/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/cache/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/cli/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/container/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/cache.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/constants.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/events.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/middleware.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/rate_limiter.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/request.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/response.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/router.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/core/server.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/crud/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/adapter.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/connection.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/firebird.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/mongodb.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/mssql.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/mysql.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/odbc.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/postgres.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/database/sqlite.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/debug/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/debug/error_overlay.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/dev_admin/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/dev_admin/metrics.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/dev_admin/plan.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/dev_admin/project_index.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/docs.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/dotenv/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/frond/FROND.md +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/frond/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/frond/engine.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/auth/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/auth/src/routes/api/gallery_auth.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/database/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/database/src/routes/api/gallery_db.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/error-overlay/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/error-overlay/src/routes/api/gallery_crash.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/orm/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/orm/src/orm/Product.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/orm/src/routes/api/gallery_products.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/queue/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/queue/src/routes/api/gallery_queue.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/rest-api/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/rest-api/src/routes/api/gallery_hello.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/templates/meta.json +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/templates/src/routes/gallery_page.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/gallery/templates/src/templates/gallery_page.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/graphql/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/i18n/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/mcp/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/mcp/protocol.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/mcp/tools.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/messenger/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/migration/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/migration/runner.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/orm/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/orm/fields.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/orm/model.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/__feedback/widget.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/css/tina4.css +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/css/tina4.min.css +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/favicon.ico +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/images/logo.svg +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/images/tina4-logo-icon.webp +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/js/frond.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/js/frond.min.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/js/tina4-dev-admin.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/js/tina4-dev-admin.min.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/js/tina4.min.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/js/tina4js.min.js +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/swagger/index.html +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/public/swagger/oauth2-redirect.html +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/query_builder/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue/job.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue/kafka_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue/lite_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue/mongo_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue/rabbitmq_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue_backends/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue_backends/kafka_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue_backends/mongo_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/queue_backends/rabbitmq_backend.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_alerts.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_badges.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_buttons.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_cards.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_forms.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_grid.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_modals.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_nav.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_reset.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_tables.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_typography.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_utilities.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/_variables.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/base.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/colors.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/scss/tina4css/tina4.scss +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/seeder/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/service/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/mongodb_handler.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/redis_handler.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/valkey_handler.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/swagger/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/components/crud.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/docker/distroless/Dockerfile +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/docker/poetry/Dockerfile +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/docker/python/Dockerfile +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/docker/uv/Dockerfile +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/302.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/401.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/403.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/404.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/500.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/502.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/503.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/errors/base.twig +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/frontend/README.md +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/readme.md +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/test_client/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/af/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/af/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/en/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/en/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/es/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/es/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/fr/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/fr/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/ja/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/ja/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/zh/LC_MESSAGES/messages.mo +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/zh/LC_MESSAGES/messages.po +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/validator/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/websocket/__init__.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/websocket/backplane.py +0 -0
- {tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/wsdl/__init__.py +0 -0
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# Tina4 xUnit-Style Testing Surface
|
|
2
|
+
"""
|
|
3
|
+
Class-based testing with positional assertions. This module exists so the
|
|
4
|
+
following pattern — used throughout the testing documentation — actually
|
|
5
|
+
runs::
|
|
6
|
+
|
|
7
|
+
from tina4_python.test import Test, assert_equal, assert_true
|
|
8
|
+
|
|
9
|
+
class BasicTest(Test):
|
|
10
|
+
|
|
11
|
+
def test_addition(self):
|
|
12
|
+
assert_equal(2 + 2, 4, "Basic addition should work")
|
|
13
|
+
|
|
14
|
+
def test_string_contains(self):
|
|
15
|
+
greeting = "Hello, World!"
|
|
16
|
+
assert_true("World" in greeting, "Greeting should contain 'World'")
|
|
17
|
+
|
|
18
|
+
The `Test` base class inherits from ``unittest.TestCase``. Pytest discovers
|
|
19
|
+
any class that inherits from ``TestCase`` regardless of the class-name
|
|
20
|
+
convention, so class names do NOT need to start with ``Test``. ``BasicTest``,
|
|
21
|
+
``UserCRUDTest``, anything works.
|
|
22
|
+
|
|
23
|
+
The assertion functions take **positional** arguments: ``(actual, expected,
|
|
24
|
+
message)``. This is deliberately different from the inline ``@tests``
|
|
25
|
+
decorator in ``tina4_python.Testing``, which uses a tuple form
|
|
26
|
+
``assert_equal((args,), expected)``. The two surfaces serve different
|
|
27
|
+
purposes:
|
|
28
|
+
|
|
29
|
+
- ``tina4_python.test`` — class-based suites, run with ``tina4 test`` or pytest
|
|
30
|
+
- ``tina4_python.Testing`` — inline assertions next to the function under test
|
|
31
|
+
|
|
32
|
+
Run any test with ``tina4 test`` or ``.venv/bin/python -m pytest tests/``.
|
|
33
|
+
"""
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import unittest
|
|
37
|
+
from typing import Any, Callable
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Test(unittest.TestCase):
|
|
41
|
+
"""Base class for Tina4 xUnit-style test suites.
|
|
42
|
+
|
|
43
|
+
Inherits from ``unittest.TestCase`` so pytest discovers any subclass by
|
|
44
|
+
inheritance — class names do not need to start with ``Test``.
|
|
45
|
+
|
|
46
|
+
**Lifecycle hooks** — Tina4 prefers snake_case names that match Python
|
|
47
|
+
style. Override either form; both are called for you::
|
|
48
|
+
|
|
49
|
+
class UserTest(Test):
|
|
50
|
+
def set_up(self): # snake_case — Tina4 idiom
|
|
51
|
+
self.user = User.create({...})
|
|
52
|
+
|
|
53
|
+
def tear_down(self): # snake_case — Tina4 idiom
|
|
54
|
+
self.user.delete()
|
|
55
|
+
|
|
56
|
+
The classic ``unittest`` ``setUp`` / ``tearDown`` still works for users
|
|
57
|
+
coming from ``unittest.TestCase``; you may use either, but do not mix
|
|
58
|
+
the two on the same class.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# ── Lifecycle adapters ──────────────────────────────────────────────
|
|
62
|
+
#
|
|
63
|
+
# unittest.TestCase only calls setUp / tearDown (camelCase). Tina4
|
|
64
|
+
# documents set_up / tear_down (snake_case). Bridge here so the
|
|
65
|
+
# snake_case methods get called when subclasses override them.
|
|
66
|
+
|
|
67
|
+
def setUp(self) -> None: # noqa: N802 — unittest convention
|
|
68
|
+
super().setUp()
|
|
69
|
+
# Only call set_up if a subclass defined it (avoids double-calling
|
|
70
|
+
# if the user uses unittest-style setUp instead).
|
|
71
|
+
if type(self).set_up is not Test.set_up:
|
|
72
|
+
self.set_up()
|
|
73
|
+
|
|
74
|
+
def tearDown(self) -> None: # noqa: N802 — unittest convention
|
|
75
|
+
if type(self).tear_down is not Test.tear_down:
|
|
76
|
+
self.tear_down()
|
|
77
|
+
super().tearDown()
|
|
78
|
+
|
|
79
|
+
def set_up(self) -> None:
|
|
80
|
+
"""Snake-case lifecycle hook — runs before each test method.
|
|
81
|
+
|
|
82
|
+
Default no-op. Override to set up per-test fixtures.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def tear_down(self) -> None:
|
|
86
|
+
"""Snake-case lifecycle hook — runs after each test method,
|
|
87
|
+
regardless of pass or fail.
|
|
88
|
+
|
|
89
|
+
Default no-op. Override to clean up per-test state.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── Assertions ──────────────────────────────────────────────────────────
|
|
94
|
+
#
|
|
95
|
+
# Each assertion follows the same shape: positional value(s) + optional
|
|
96
|
+
# message. They raise ``AssertionError`` on failure, which pytest and the
|
|
97
|
+
# ``tina4 test`` CLI both render with the trailing message.
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ── Argument-shape helper ───────────────────────────────────────────────
|
|
101
|
+
#
|
|
102
|
+
# Every assertion below accepts a UNIFORM (actual, expected, message)
|
|
103
|
+
# signature so the API reads the same regardless of which assertion you
|
|
104
|
+
# pick. For binary assertions (assert_true, assert_false, assert_none,
|
|
105
|
+
# assert_not_none) the ``expected`` slot is redundant — it states the
|
|
106
|
+
# obvious — but spelling it out keeps the call shape consistent with
|
|
107
|
+
# assert_equal / assert_not_equal where it carries real information.
|
|
108
|
+
#
|
|
109
|
+
# Two-argument legacy calls (value, message) are still accepted because
|
|
110
|
+
# the chapter's examples shipped that way for years. The helper detects
|
|
111
|
+
# the form by argument type:
|
|
112
|
+
# assert_false(condition, "message") → 2-arg legacy form
|
|
113
|
+
# assert_false(condition, False, "message") → 3-arg uniform form
|
|
114
|
+
# When the second positional arg is a string AND no third arg was given,
|
|
115
|
+
# we treat it as the message.
|
|
116
|
+
|
|
117
|
+
_SENTINEL = object()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _split_expected_message(
|
|
121
|
+
expected_or_message: Any, message: str, default_expected: Any
|
|
122
|
+
) -> tuple[Any, str]:
|
|
123
|
+
"""Split a 2-arg vs 3-arg call into (expected, message)."""
|
|
124
|
+
if message:
|
|
125
|
+
# Caller passed all three positional args — honour them as-given.
|
|
126
|
+
return expected_or_message, message
|
|
127
|
+
if expected_or_message is _SENTINEL:
|
|
128
|
+
# Caller passed only the first positional — no message, no expected.
|
|
129
|
+
return default_expected, ""
|
|
130
|
+
if isinstance(expected_or_message, str):
|
|
131
|
+
# Two-arg legacy form: the second arg is the message.
|
|
132
|
+
return default_expected, expected_or_message
|
|
133
|
+
# Two-arg uniform form: second arg is the expected value, no message.
|
|
134
|
+
return expected_or_message, ""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def assert_equal(actual: Any, expected: Any, message: str = "") -> None:
|
|
138
|
+
"""Assert ``actual == expected``.
|
|
139
|
+
|
|
140
|
+
Signature: ``assert_equal(actual, expected, message="")``
|
|
141
|
+
"""
|
|
142
|
+
if actual != expected:
|
|
143
|
+
raise AssertionError(
|
|
144
|
+
message or f"Expected {expected!r}, got {actual!r}"
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def assert_not_equal(actual: Any, expected: Any, message: str = "") -> None:
|
|
149
|
+
"""Assert ``actual != expected``.
|
|
150
|
+
|
|
151
|
+
Signature: ``assert_not_equal(actual, expected, message="")``
|
|
152
|
+
"""
|
|
153
|
+
if actual == expected:
|
|
154
|
+
raise AssertionError(
|
|
155
|
+
message or f"Expected {actual!r} != {expected!r}, but they are equal"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def assert_true(actual: Any, expected: Any = _SENTINEL, message: str = "") -> None:
|
|
160
|
+
"""Assert ``actual`` is truthy.
|
|
161
|
+
|
|
162
|
+
Signature: ``assert_true(actual, expected=True, message="")``
|
|
163
|
+
|
|
164
|
+
The ``expected`` slot is there for parity with ``assert_equal`` — pass
|
|
165
|
+
``True`` to spell it out, or omit it and pass the message directly.
|
|
166
|
+
Both forms work::
|
|
167
|
+
|
|
168
|
+
assert_true(x, "should be truthy") # legacy
|
|
169
|
+
assert_true(x, True, "should be truthy") # uniform
|
|
170
|
+
"""
|
|
171
|
+
expected, message = _split_expected_message(expected, message, True)
|
|
172
|
+
if not actual:
|
|
173
|
+
raise AssertionError(
|
|
174
|
+
message or f"Expected truthy ({expected!r}), got {actual!r}"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def assert_false(actual: Any, expected: Any = _SENTINEL, message: str = "") -> None:
|
|
179
|
+
"""Assert ``actual`` is falsy.
|
|
180
|
+
|
|
181
|
+
Signature: ``assert_false(actual, expected=False, message="")``
|
|
182
|
+
|
|
183
|
+
The ``expected`` slot is there for parity with ``assert_equal`` — pass
|
|
184
|
+
``False`` to spell it out, or omit it and pass the message directly.
|
|
185
|
+
Both forms work::
|
|
186
|
+
|
|
187
|
+
assert_false(x, "should be falsy") # legacy
|
|
188
|
+
assert_false(x, False, "should be falsy") # uniform
|
|
189
|
+
"""
|
|
190
|
+
expected, message = _split_expected_message(expected, message, False)
|
|
191
|
+
if actual:
|
|
192
|
+
raise AssertionError(
|
|
193
|
+
message or f"Expected falsy ({expected!r}), got {actual!r}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def assert_none(value: Any, expected: Any = _SENTINEL, message: str = "") -> None:
|
|
198
|
+
"""Assert ``value`` is ``None``.
|
|
199
|
+
|
|
200
|
+
Signature: ``assert_none(actual, expected=None, message="")``
|
|
201
|
+
"""
|
|
202
|
+
expected, message = _split_expected_message(expected, message, None)
|
|
203
|
+
if value is not None:
|
|
204
|
+
raise AssertionError(
|
|
205
|
+
message or f"Expected None, got {value!r}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def assert_not_none(value: Any, expected: Any = _SENTINEL, message: str = "") -> None:
|
|
210
|
+
"""Assert ``value`` is not ``None``.
|
|
211
|
+
|
|
212
|
+
Signature: ``assert_not_none(actual, expected="not None", message="")``
|
|
213
|
+
"""
|
|
214
|
+
expected, message = _split_expected_message(expected, message, "not None")
|
|
215
|
+
if value is None:
|
|
216
|
+
raise AssertionError(message or "Expected not None, got None")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def assert_in(item: Any, container: Any, message: str = "") -> None:
|
|
220
|
+
"""Assert ``item`` is contained in ``container``.
|
|
221
|
+
|
|
222
|
+
Signature: ``assert_in(item, container, message="")``
|
|
223
|
+
"""
|
|
224
|
+
if item not in container:
|
|
225
|
+
raise AssertionError(
|
|
226
|
+
message or f"Expected {item!r} in {container!r}"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def assert_not_in(item: Any, container: Any, message: str = "") -> None:
|
|
231
|
+
"""Assert ``item`` is not in ``container``.
|
|
232
|
+
|
|
233
|
+
Signature: ``assert_not_in(item, container, message="")``
|
|
234
|
+
"""
|
|
235
|
+
if item in container:
|
|
236
|
+
raise AssertionError(
|
|
237
|
+
message or f"Expected {item!r} not in {container!r}"
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def assert_is_instance(value: Any, expected_type: type, message: str = "") -> None:
|
|
242
|
+
"""Assert ``value`` is an instance of ``expected_type``.
|
|
243
|
+
|
|
244
|
+
Signature: ``assert_is_instance(value, expected_type, message="")``
|
|
245
|
+
"""
|
|
246
|
+
if not isinstance(value, expected_type):
|
|
247
|
+
raise AssertionError(
|
|
248
|
+
message
|
|
249
|
+
or f"Expected instance of {expected_type.__name__}, got {type(value).__name__}"
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def assert_greater(actual: Any, expected: Any, message: str = "") -> None:
|
|
254
|
+
"""Assert ``actual > expected``.
|
|
255
|
+
|
|
256
|
+
Signature: ``assert_greater(actual, expected, message="")``
|
|
257
|
+
"""
|
|
258
|
+
if not (actual > expected):
|
|
259
|
+
raise AssertionError(message or f"Expected {actual!r} > {expected!r}")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def assert_less(actual: Any, expected: Any, message: str = "") -> None:
|
|
263
|
+
"""Assert ``actual < expected``.
|
|
264
|
+
|
|
265
|
+
Signature: ``assert_less(actual, expected, message="")``
|
|
266
|
+
"""
|
|
267
|
+
if not (actual < expected):
|
|
268
|
+
raise AssertionError(message or f"Expected {actual!r} < {expected!r}")
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def assert_almost_equal(
|
|
272
|
+
actual: float, expected: float, places: int = 7, message: str = ""
|
|
273
|
+
) -> None:
|
|
274
|
+
"""Assert two floats are equal to ``places`` decimal places.
|
|
275
|
+
|
|
276
|
+
Signature: ``assert_almost_equal(actual, expected, places=7, message="")``
|
|
277
|
+
|
|
278
|
+
Useful for floating-point comparisons where exact equality is unreliable.
|
|
279
|
+
"""
|
|
280
|
+
if round(abs(actual - expected), places) != 0:
|
|
281
|
+
raise AssertionError(
|
|
282
|
+
message
|
|
283
|
+
or f"Expected {expected!r} ≈ {actual!r} (within {places} places)"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def assert_raises(
|
|
288
|
+
callable_or_exception: Callable | type,
|
|
289
|
+
exception_class: type | None = None,
|
|
290
|
+
message: str = "",
|
|
291
|
+
):
|
|
292
|
+
"""Assert that a callable raises a specific exception.
|
|
293
|
+
|
|
294
|
+
Three forms, all supported::
|
|
295
|
+
|
|
296
|
+
# 1. Docs form — callable first, exception second, optional message
|
|
297
|
+
assert_raises(lambda: int("not a number"), ValueError, "must raise")
|
|
298
|
+
|
|
299
|
+
# 2. Context manager — useful when the body is more than one line
|
|
300
|
+
with assert_raises(ValueError):
|
|
301
|
+
value = parse(input)
|
|
302
|
+
process(value)
|
|
303
|
+
|
|
304
|
+
# 3. unittest.TestCase compatibility — exception first form
|
|
305
|
+
assert_raises(ValueError, lambda: int("not a number"))
|
|
306
|
+
|
|
307
|
+
Raises ``AssertionError`` if no exception is raised, or if a different
|
|
308
|
+
exception type is raised.
|
|
309
|
+
"""
|
|
310
|
+
# Form 2: context manager — caller passed just the exception class
|
|
311
|
+
if exception_class is None:
|
|
312
|
+
if not isinstance(callable_or_exception, type) or not issubclass(
|
|
313
|
+
callable_or_exception, BaseException
|
|
314
|
+
):
|
|
315
|
+
raise TypeError(
|
|
316
|
+
"assert_raises with one argument requires an exception class"
|
|
317
|
+
)
|
|
318
|
+
return _RaisesContext(callable_or_exception)
|
|
319
|
+
|
|
320
|
+
# Determine which argument is the callable and which is the exception
|
|
321
|
+
# by type — supports docs form AND unittest order without ambiguity.
|
|
322
|
+
if isinstance(callable_or_exception, type) and issubclass(
|
|
323
|
+
callable_or_exception, BaseException
|
|
324
|
+
):
|
|
325
|
+
# Form 3: assert_raises(ExceptionType, callable)
|
|
326
|
+
exc_type = callable_or_exception
|
|
327
|
+
fn = exception_class
|
|
328
|
+
else:
|
|
329
|
+
# Form 1: assert_raises(callable, ExceptionType, message)
|
|
330
|
+
fn = callable_or_exception
|
|
331
|
+
exc_type = exception_class
|
|
332
|
+
|
|
333
|
+
if not callable(fn):
|
|
334
|
+
raise TypeError("assert_raises requires a callable")
|
|
335
|
+
|
|
336
|
+
try:
|
|
337
|
+
fn()
|
|
338
|
+
except exc_type:
|
|
339
|
+
return
|
|
340
|
+
except Exception as e:
|
|
341
|
+
raise AssertionError(
|
|
342
|
+
message
|
|
343
|
+
or f"Expected {exc_type.__name__}, got {type(e).__name__}: {e}"
|
|
344
|
+
)
|
|
345
|
+
raise AssertionError(
|
|
346
|
+
message
|
|
347
|
+
or f"Expected {exc_type.__name__} to be raised, but nothing was"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
class _RaisesContext:
|
|
352
|
+
"""Context-manager form of ``assert_raises``."""
|
|
353
|
+
|
|
354
|
+
def __init__(self, exception_class: type) -> None:
|
|
355
|
+
self.exception_class = exception_class
|
|
356
|
+
self.exception: BaseException | None = None
|
|
357
|
+
|
|
358
|
+
def __enter__(self) -> "_RaisesContext":
|
|
359
|
+
return self
|
|
360
|
+
|
|
361
|
+
def __exit__(self, exc_type, exc_val, exc_tb) -> bool:
|
|
362
|
+
if exc_type is None:
|
|
363
|
+
raise AssertionError(
|
|
364
|
+
f"Expected {self.exception_class.__name__} to be raised, but nothing was"
|
|
365
|
+
)
|
|
366
|
+
if not issubclass(exc_type, self.exception_class):
|
|
367
|
+
return False # let it propagate — wrong exception type
|
|
368
|
+
self.exception = exc_val
|
|
369
|
+
return True # swallow the expected exception
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
__all__ = [
|
|
373
|
+
# Base class
|
|
374
|
+
"Test",
|
|
375
|
+
# Assertions — positional (actual, expected, message)
|
|
376
|
+
"assert_equal",
|
|
377
|
+
"assert_not_equal",
|
|
378
|
+
"assert_true",
|
|
379
|
+
"assert_false",
|
|
380
|
+
"assert_none",
|
|
381
|
+
"assert_not_none",
|
|
382
|
+
"assert_in",
|
|
383
|
+
"assert_not_in",
|
|
384
|
+
"assert_is_instance",
|
|
385
|
+
"assert_greater",
|
|
386
|
+
"assert_less",
|
|
387
|
+
"assert_almost_equal",
|
|
388
|
+
"assert_raises",
|
|
389
|
+
]
|
|
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
|
|
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.12.13 → tina4_python-3.12.14}/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
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/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.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/mongodb_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/redis_handler.py
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/session_handlers/valkey_handler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/docker/distroless/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/templates/docker/poetry/Dockerfile
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/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.12.13 → tina4_python-3.12.14}/tina4_python/translations/af/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/af/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/en/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/en/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/es/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/es/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/fr/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/fr/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/ja/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/ja/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/zh/LC_MESSAGES/messages.mo
RENAMED
|
File without changes
|
{tina4_python-3.12.13 → tina4_python-3.12.14}/tina4_python/translations/zh/LC_MESSAGES/messages.po
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|