tigrbl-tests 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl
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.
- tests/i9n/test_tigrbl_api_app_usage_uvicorn.py +98 -0
- tests/i9n/test_tigrbl_api_usage_uvicorn.py +105 -0
- tests/i9n/test_tigrbl_api_uvicorn.py +67 -0
- tests/i9n/test_tigrbl_app_include_api_uvicorn.py +149 -0
- tests/i9n/test_tigrbl_app_multi_api_uvicorn.py +80 -0
- tests/i9n/test_tigrbl_app_usage_uvicorn.py +103 -0
- tests/i9n/test_tigrbl_app_uvicorn.py +58 -0
- tests/unit/test_initialize_async_task.py +27 -0
- tests/unit/test_initialize_mixed_engines.py +41 -0
- tests/unit/test_initialize_task_schedule.py +27 -0
- tests/unit/test_spec_app.py +1 -1
- tests/unit/test_table_collect_spec.py +1 -1
- tests/unit/test_table_namespace_init.py +27 -0
- tests/unit/test_table_namespace_isolation.py +43 -0
- tests/unit/test_tigrbl_api_app_configuration.py +83 -0
- tests/unit/test_tigrbl_api_app_instantiation.py +42 -0
- tests/unit/test_tigrbl_api_app_subclass_definition.py +38 -0
- tests/unit/test_tigrbl_api_configuration.py +47 -0
- tests/unit/test_tigrbl_api_instantiation.py +37 -0
- tests/unit/test_tigrbl_api_subclass_definition.py +37 -0
- tests/unit/test_tigrbl_app_configuration.py +47 -0
- tests/unit/test_tigrbl_app_instantiation.py +37 -0
- tests/unit/test_tigrbl_app_subclass_definition.py +37 -0
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/METADATA +16 -1
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/RECORD +27 -6
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/WHEEL +0 -0
- {tigrbl_tests-0.3.2.dist-info → tigrbl_tests-0.3.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from sqlalchemy import Column, Integer
|
|
5
|
+
|
|
6
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
7
|
+
from tigrbl.engine.shortcuts import mem
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AsyncWidget(Base):
|
|
11
|
+
__tablename__ = "widgets_async_api"
|
|
12
|
+
|
|
13
|
+
id = Column(Integer, primary_key=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SyncWidget(Base):
|
|
17
|
+
__tablename__ = "widgets_sync_api"
|
|
18
|
+
|
|
19
|
+
id = Column(Integer, primary_key=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.asyncio
|
|
23
|
+
async def test_initialize_handles_mixed_sync_async_apis():
|
|
24
|
+
async_api = TigrblApi(engine=mem())
|
|
25
|
+
async_api.include_model(AsyncWidget, prefix="")
|
|
26
|
+
|
|
27
|
+
sync_api = TigrblApi(engine=mem(async_=False))
|
|
28
|
+
sync_api.include_model(SyncWidget, prefix="")
|
|
29
|
+
|
|
30
|
+
app = TigrblApp(engine=mem(async_=False))
|
|
31
|
+
app.include_apis([async_api, sync_api])
|
|
32
|
+
|
|
33
|
+
result = app.initialize()
|
|
34
|
+
|
|
35
|
+
assert isinstance(result, asyncio.Task)
|
|
36
|
+
|
|
37
|
+
await result
|
|
38
|
+
|
|
39
|
+
assert getattr(app, "_ddl_executed", False) is True
|
|
40
|
+
assert getattr(async_api, "_ddl_executed", False) is True
|
|
41
|
+
assert getattr(sync_api, "_ddl_executed", False) is True
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from sqlalchemy import Column, Integer
|
|
5
|
+
|
|
6
|
+
from tigrbl import Base, TigrblApp
|
|
7
|
+
from tigrbl.engine.shortcuts import mem
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Widget(Base):
|
|
11
|
+
__tablename__ = "widgets"
|
|
12
|
+
|
|
13
|
+
id = Column(Integer, primary_key=True)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_initialize_schedules_task_for_sync_engine():
|
|
18
|
+
api = TigrblApp(engine=mem(async_=False))
|
|
19
|
+
api.models["Widget"] = Widget
|
|
20
|
+
|
|
21
|
+
result = api.initialize()
|
|
22
|
+
|
|
23
|
+
assert isinstance(result, asyncio.Task)
|
|
24
|
+
|
|
25
|
+
await result
|
|
26
|
+
|
|
27
|
+
assert getattr(api, "_ddl_executed", False) is True
|
tests/unit/test_spec_app.py
CHANGED
|
@@ -15,7 +15,7 @@ def test_app_spec_defaults_and_merge():
|
|
|
15
15
|
spec = mro_collect_app_spec(ChildApp)
|
|
16
16
|
assert spec.title == "Base"
|
|
17
17
|
assert spec.version == "1.0"
|
|
18
|
-
assert spec.apis == ("
|
|
18
|
+
assert spec.apis == ("child", "base")
|
|
19
19
|
assert spec.ops == ("read",)
|
|
20
20
|
assert spec.models == ()
|
|
21
21
|
assert spec.schemas == ()
|
|
@@ -32,7 +32,7 @@ class Model(SpecA, SpecB, Base, GUIDPk):
|
|
|
32
32
|
def test_collect_table_spec_merges_mro():
|
|
33
33
|
spec = mro_collect_table_spec(Model)
|
|
34
34
|
assert spec.model is Model
|
|
35
|
-
assert spec.engine == "
|
|
35
|
+
assert spec.engine == "db_a"
|
|
36
36
|
assert spec.ops == ("a", "b")
|
|
37
37
|
assert spec.columns == ("col_a", "col_b")
|
|
38
38
|
assert spec.schemas == ("SchemaA", "SchemaB")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
|
|
3
|
+
from tigrbl.specs import F, S, acol
|
|
4
|
+
from tigrbl.table import Table
|
|
5
|
+
from tigrbl.types import Integer, Mapped
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Widget(Table):
|
|
9
|
+
__tablename__ = "widgets_namespace_init"
|
|
10
|
+
|
|
11
|
+
id: Mapped[int] = acol(
|
|
12
|
+
storage=S(type_=Integer, primary_key=True), field=F(py_type=int)
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_table_initializes_model_namespaces() -> None:
|
|
17
|
+
assert isinstance(Widget.ops, SimpleNamespace)
|
|
18
|
+
assert Widget.ops is Widget.opspecs
|
|
19
|
+
assert isinstance(Widget.schemas, SimpleNamespace)
|
|
20
|
+
assert isinstance(Widget.hooks, SimpleNamespace)
|
|
21
|
+
assert isinstance(Widget.handlers, SimpleNamespace)
|
|
22
|
+
assert isinstance(Widget.rpc, SimpleNamespace)
|
|
23
|
+
assert isinstance(Widget.rest, SimpleNamespace)
|
|
24
|
+
assert hasattr(Widget.rest, "router")
|
|
25
|
+
assert isinstance(Widget.__tigrbl_hooks__, dict)
|
|
26
|
+
assert isinstance(Widget.columns, SimpleNamespace)
|
|
27
|
+
assert Widget.columns.id is Widget.__tigrbl_cols__["id"]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from types import SimpleNamespace
|
|
2
|
+
|
|
3
|
+
from tigrbl.specs import F, S, acol
|
|
4
|
+
from tigrbl.table import Table
|
|
5
|
+
from tigrbl.types import Integer, Mapped
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseWidget(Table):
|
|
9
|
+
__abstract__ = True
|
|
10
|
+
|
|
11
|
+
id: Mapped[int] = acol(
|
|
12
|
+
storage=S(type_=Integer, primary_key=True), field=F(py_type=int)
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WidgetAlpha(BaseWidget):
|
|
17
|
+
__tablename__ = "widgets_namespace_alpha"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class WidgetBeta(BaseWidget):
|
|
21
|
+
__tablename__ = "widgets_namespace_beta"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_model_namespaces_are_per_class() -> None:
|
|
25
|
+
assert isinstance(WidgetAlpha.ops, SimpleNamespace)
|
|
26
|
+
assert isinstance(WidgetBeta.ops, SimpleNamespace)
|
|
27
|
+
assert WidgetAlpha.ops is WidgetAlpha.opspecs
|
|
28
|
+
assert WidgetBeta.ops is WidgetBeta.opspecs
|
|
29
|
+
assert WidgetAlpha.ops is not WidgetBeta.ops
|
|
30
|
+
assert WidgetAlpha.schemas is not WidgetBeta.schemas
|
|
31
|
+
assert WidgetAlpha.hooks is not WidgetBeta.hooks
|
|
32
|
+
assert WidgetAlpha.handlers is not WidgetBeta.handlers
|
|
33
|
+
assert WidgetAlpha.rpc is not WidgetBeta.rpc
|
|
34
|
+
assert WidgetAlpha.rest is not WidgetBeta.rest
|
|
35
|
+
assert WidgetAlpha.__tigrbl_hooks__ is not WidgetBeta.__tigrbl_hooks__
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_model_namespace_mutations_do_not_bleed() -> None:
|
|
39
|
+
WidgetAlpha.ops.by_alias["alpha_only"] = ["alpha"]
|
|
40
|
+
assert "alpha_only" not in WidgetBeta.ops.by_alias
|
|
41
|
+
|
|
42
|
+
WidgetAlpha.schemas.alpha_only = "alpha_schema"
|
|
43
|
+
assert not hasattr(WidgetBeta.schemas, "alpha_only")
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from fastapi import Security
|
|
3
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
8
|
+
from tigrbl.specs import F, IO, S, acol
|
|
9
|
+
from tigrbl.types import Mapped, String
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _auth_dependency(
|
|
13
|
+
credentials: HTTPAuthorizationCredentials = Security(HTTPBearer()),
|
|
14
|
+
) -> HTTPAuthorizationCredentials:
|
|
15
|
+
return credentials
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Iota(Base, GUIDPk):
|
|
19
|
+
__tablename__ = "iota_api_app_cfg"
|
|
20
|
+
__allow_unmapped__ = True
|
|
21
|
+
|
|
22
|
+
name: Mapped[str] = acol(
|
|
23
|
+
storage=S(type_=String, nullable=False),
|
|
24
|
+
field=F(py_type=str),
|
|
25
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class IotaApi(TigrblApi):
|
|
32
|
+
MODELS = (Iota,)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.unit
|
|
36
|
+
def test_tigrbl_api_app_constructor_configuration_applies_metadata() -> None:
|
|
37
|
+
api = IotaApi(
|
|
38
|
+
engine=mem(async_=False),
|
|
39
|
+
jsonrpc_prefix="/rpcx",
|
|
40
|
+
system_prefix="/systemx",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
class IotaApp(TigrblApp):
|
|
44
|
+
APIS = (api,)
|
|
45
|
+
|
|
46
|
+
app = IotaApp(engine=mem(async_=False), title="Iota App", version="9.9.9")
|
|
47
|
+
|
|
48
|
+
api_dir = dir(api)
|
|
49
|
+
app_dir = dir(app)
|
|
50
|
+
|
|
51
|
+
assert "jsonrpc_prefix" in api_dir
|
|
52
|
+
assert "system_prefix" in api_dir
|
|
53
|
+
assert "TITLE" in app_dir
|
|
54
|
+
assert "VERSION" in app_dir
|
|
55
|
+
assert api.jsonrpc_prefix == "/rpcx"
|
|
56
|
+
assert api.system_prefix == "/systemx"
|
|
57
|
+
assert app.TITLE == "Iota App"
|
|
58
|
+
assert app.VERSION == "9.9.9"
|
|
59
|
+
assert app.apis == [api]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.unit
|
|
63
|
+
def test_tigrbl_api_app_post_instantiation_updates_auth_state() -> None:
|
|
64
|
+
api = IotaApi(engine=mem(async_=False))
|
|
65
|
+
|
|
66
|
+
class IotaApp(TigrblApp):
|
|
67
|
+
APIS = (api,)
|
|
68
|
+
|
|
69
|
+
app = IotaApp(engine=mem(async_=False))
|
|
70
|
+
api.set_auth(authn=_auth_dependency, allow_anon=False)
|
|
71
|
+
app.set_auth(authn=_auth_dependency, allow_anon=False)
|
|
72
|
+
|
|
73
|
+
api_dir = dir(api)
|
|
74
|
+
app_dir = dir(app)
|
|
75
|
+
|
|
76
|
+
assert "_authn" in api_dir
|
|
77
|
+
assert "_allow_anon" in api_dir
|
|
78
|
+
assert api._authn is _auth_dependency
|
|
79
|
+
assert api._allow_anon is False
|
|
80
|
+
assert "_authn" in app_dir
|
|
81
|
+
assert "_allow_anon" in app_dir
|
|
82
|
+
assert app._authn is _auth_dependency
|
|
83
|
+
assert app._allow_anon is False
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
4
|
+
from tigrbl.engine.shortcuts import mem
|
|
5
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
6
|
+
from tigrbl.specs import F, IO, S, acol
|
|
7
|
+
from tigrbl.types import Mapped, String
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Theta(Base, GUIDPk):
|
|
11
|
+
__tablename__ = "theta_api_app_inst"
|
|
12
|
+
__allow_unmapped__ = True
|
|
13
|
+
|
|
14
|
+
name: Mapped[str] = acol(
|
|
15
|
+
storage=S(type_=String, nullable=False),
|
|
16
|
+
field=F(py_type=str),
|
|
17
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ThetaApi(TigrblApi):
|
|
24
|
+
MODELS = (Theta,)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.unit
|
|
28
|
+
def test_tigrbl_api_app_instantiation_sets_composed_state() -> None:
|
|
29
|
+
api = ThetaApi(engine=mem(async_=False))
|
|
30
|
+
|
|
31
|
+
class ThetaApp(TigrblApp):
|
|
32
|
+
APIS = (api,)
|
|
33
|
+
|
|
34
|
+
app = ThetaApp(engine=mem(async_=False))
|
|
35
|
+
|
|
36
|
+
api_dir = dir(api)
|
|
37
|
+
app_dir = dir(app)
|
|
38
|
+
|
|
39
|
+
assert "models" in api_dir
|
|
40
|
+
assert api.models["Theta"] is Theta
|
|
41
|
+
assert "apis" in app_dir
|
|
42
|
+
assert app.apis == [api]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
4
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
5
|
+
from tigrbl.specs import F, IO, S, acol
|
|
6
|
+
from tigrbl.types import Mapped, String
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Zeta(Base, GUIDPk):
|
|
10
|
+
__tablename__ = "zeta_api_app_decl"
|
|
11
|
+
__allow_unmapped__ = True
|
|
12
|
+
|
|
13
|
+
name: Mapped[str] = acol(
|
|
14
|
+
storage=S(type_=String, nullable=False),
|
|
15
|
+
field=F(py_type=str),
|
|
16
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ZetaApi(TigrblApi):
|
|
23
|
+
MODELS = (Zeta,)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ZetaApp(TigrblApp):
|
|
27
|
+
APIS = (ZetaApi,)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.mark.unit
|
|
31
|
+
def test_tigrbl_api_app_subclass_declares_composition() -> None:
|
|
32
|
+
api_dir = dir(ZetaApi)
|
|
33
|
+
app_dir = dir(ZetaApp)
|
|
34
|
+
|
|
35
|
+
assert "MODELS" in api_dir
|
|
36
|
+
assert "APIS" in app_dir
|
|
37
|
+
assert ZetaApi.MODELS == (Zeta,)
|
|
38
|
+
assert ZetaApp.APIS == (ZetaApi,)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from fastapi import Security
|
|
3
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
+
|
|
5
|
+
from tigrbl import TigrblApi
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _auth_dependency(
|
|
10
|
+
credentials: HTTPAuthorizationCredentials = Security(HTTPBearer()),
|
|
11
|
+
) -> HTTPAuthorizationCredentials:
|
|
12
|
+
return credentials
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.unit
|
|
16
|
+
def test_tigrbl_api_constructor_configuration_sets_prefixes() -> None:
|
|
17
|
+
def sample_hook() -> None:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
api = TigrblApi(
|
|
21
|
+
engine=mem(async_=False),
|
|
22
|
+
jsonrpc_prefix="/rpcx",
|
|
23
|
+
system_prefix="/systemx",
|
|
24
|
+
api_hooks={"*": {"pre": [sample_hook]}},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
api_dir = dir(api)
|
|
28
|
+
|
|
29
|
+
assert "jsonrpc_prefix" in api_dir
|
|
30
|
+
assert "system_prefix" in api_dir
|
|
31
|
+
assert "_api_hooks_map" in api_dir
|
|
32
|
+
assert api.jsonrpc_prefix == "/rpcx"
|
|
33
|
+
assert api.system_prefix == "/systemx"
|
|
34
|
+
assert api._api_hooks_map == {"*": {"pre": [sample_hook]}}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.unit
|
|
38
|
+
def test_tigrbl_api_post_instantiation_set_auth_updates_state() -> None:
|
|
39
|
+
api = TigrblApi(engine=mem(async_=False))
|
|
40
|
+
api.set_auth(authn=_auth_dependency, allow_anon=False)
|
|
41
|
+
|
|
42
|
+
api_dir = dir(api)
|
|
43
|
+
|
|
44
|
+
assert "_authn" in api_dir
|
|
45
|
+
assert "_allow_anon" in api_dir
|
|
46
|
+
assert api._authn is _auth_dependency
|
|
47
|
+
assert api._allow_anon is False
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tigrbl import Base, TigrblApi
|
|
4
|
+
from tigrbl.engine.shortcuts import mem
|
|
5
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
6
|
+
from tigrbl.specs import F, IO, S, acol
|
|
7
|
+
from tigrbl.types import Mapped, String
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Widget(Base, GUIDPk):
|
|
11
|
+
__tablename__ = "widgets_api_inst"
|
|
12
|
+
__allow_unmapped__ = True
|
|
13
|
+
|
|
14
|
+
name: Mapped[str] = acol(
|
|
15
|
+
storage=S(type_=String, nullable=False),
|
|
16
|
+
field=F(py_type=str),
|
|
17
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WidgetApi(TigrblApi):
|
|
24
|
+
MODELS = (Widget,)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.unit
|
|
28
|
+
def test_tigrbl_api_instantiation_sets_containers() -> None:
|
|
29
|
+
api = WidgetApi(engine=mem(async_=False))
|
|
30
|
+
api_dir = dir(api)
|
|
31
|
+
|
|
32
|
+
assert "models" in api_dir
|
|
33
|
+
assert "routers" in api_dir
|
|
34
|
+
assert "schemas" in api_dir
|
|
35
|
+
assert "jsonrpc_prefix" in api_dir
|
|
36
|
+
assert "system_prefix" in api_dir
|
|
37
|
+
assert api.models["Widget"] is Widget
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tigrbl import Base, TigrblApi
|
|
4
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
5
|
+
from tigrbl.specs import F, IO, S, acol
|
|
6
|
+
from tigrbl.types import Mapped, String
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Widget(Base, GUIDPk):
|
|
10
|
+
__tablename__ = "widgets_api_decl"
|
|
11
|
+
__allow_unmapped__ = True
|
|
12
|
+
|
|
13
|
+
name: Mapped[str] = acol(
|
|
14
|
+
storage=S(type_=String, nullable=False),
|
|
15
|
+
field=F(py_type=str),
|
|
16
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WidgetApi(TigrblApi):
|
|
23
|
+
PREFIX = "/widgets"
|
|
24
|
+
TAGS = ("widgets",)
|
|
25
|
+
MODELS = (Widget,)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.unit
|
|
29
|
+
def test_tigrbl_api_subclass_declares_metadata() -> None:
|
|
30
|
+
class_dir = dir(WidgetApi)
|
|
31
|
+
|
|
32
|
+
assert "MODELS" in class_dir
|
|
33
|
+
assert "TAGS" in class_dir
|
|
34
|
+
assert "PREFIX" in class_dir
|
|
35
|
+
assert WidgetApi.MODELS == (Widget,)
|
|
36
|
+
assert WidgetApi.TAGS == ("widgets",)
|
|
37
|
+
assert WidgetApi.PREFIX == "/widgets"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from fastapi import Security
|
|
3
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
4
|
+
|
|
5
|
+
from tigrbl import TigrblApp
|
|
6
|
+
from tigrbl.engine.shortcuts import mem
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _auth_dependency(
|
|
10
|
+
credentials: HTTPAuthorizationCredentials = Security(HTTPBearer()),
|
|
11
|
+
) -> HTTPAuthorizationCredentials:
|
|
12
|
+
return credentials
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.unit
|
|
16
|
+
def test_tigrbl_app_constructor_configuration_sets_metadata() -> None:
|
|
17
|
+
app = TigrblApp(
|
|
18
|
+
engine=mem(async_=False),
|
|
19
|
+
title="Configured App",
|
|
20
|
+
version="2.3.4",
|
|
21
|
+
jsonrpc_prefix="/rpcx",
|
|
22
|
+
system_prefix="/systemx",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
app_dir = dir(app)
|
|
26
|
+
|
|
27
|
+
assert "TITLE" in app_dir
|
|
28
|
+
assert "VERSION" in app_dir
|
|
29
|
+
assert "jsonrpc_prefix" in app_dir
|
|
30
|
+
assert "system_prefix" in app_dir
|
|
31
|
+
assert app.TITLE == "Configured App"
|
|
32
|
+
assert app.VERSION == "2.3.4"
|
|
33
|
+
assert app.jsonrpc_prefix == "/rpcx"
|
|
34
|
+
assert app.system_prefix == "/systemx"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@pytest.mark.unit
|
|
38
|
+
def test_tigrbl_app_post_instantiation_set_auth_updates_state() -> None:
|
|
39
|
+
app = TigrblApp(engine=mem(async_=False))
|
|
40
|
+
app.set_auth(authn=_auth_dependency, allow_anon=False)
|
|
41
|
+
|
|
42
|
+
app_dir = dir(app)
|
|
43
|
+
|
|
44
|
+
assert "_authn" in app_dir
|
|
45
|
+
assert "_allow_anon" in app_dir
|
|
46
|
+
assert app._authn is _auth_dependency
|
|
47
|
+
assert app._allow_anon is False
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tigrbl import Base, TigrblApp
|
|
4
|
+
from tigrbl.engine.shortcuts import mem
|
|
5
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
6
|
+
from tigrbl.specs import F, IO, S, acol
|
|
7
|
+
from tigrbl.types import Mapped, String
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Widget(Base, GUIDPk):
|
|
11
|
+
__tablename__ = "widgets_app_inst"
|
|
12
|
+
__allow_unmapped__ = True
|
|
13
|
+
|
|
14
|
+
name: Mapped[str] = acol(
|
|
15
|
+
storage=S(type_=String, nullable=False),
|
|
16
|
+
field=F(py_type=str),
|
|
17
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WidgetApp(TigrblApp):
|
|
24
|
+
MODELS = (Widget,)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.unit
|
|
28
|
+
def test_tigrbl_app_instantiation_sets_containers() -> None:
|
|
29
|
+
app = WidgetApp(engine=mem(async_=False))
|
|
30
|
+
app_dir = dir(app)
|
|
31
|
+
|
|
32
|
+
assert "models" in app_dir
|
|
33
|
+
assert "routers" in app_dir
|
|
34
|
+
assert "schemas" in app_dir
|
|
35
|
+
assert "jsonrpc_prefix" in app_dir
|
|
36
|
+
assert "system_prefix" in app_dir
|
|
37
|
+
assert app.models["Widget"] is Widget
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from tigrbl import Base, TigrblApp
|
|
4
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
5
|
+
from tigrbl.specs import F, IO, S, acol
|
|
6
|
+
from tigrbl.types import Mapped, String
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Widget(Base, GUIDPk):
|
|
10
|
+
__tablename__ = "widgets_app_decl"
|
|
11
|
+
__allow_unmapped__ = True
|
|
12
|
+
|
|
13
|
+
name: Mapped[str] = acol(
|
|
14
|
+
storage=S(type_=String, nullable=False),
|
|
15
|
+
field=F(py_type=str),
|
|
16
|
+
io=IO(in_verbs=("create",), out_verbs=("read", "list")),
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WidgetApp(TigrblApp):
|
|
23
|
+
TITLE = "Widget App"
|
|
24
|
+
VERSION = "1.0.0"
|
|
25
|
+
MODELS = (Widget,)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.unit
|
|
29
|
+
def test_tigrbl_app_subclass_declares_metadata() -> None:
|
|
30
|
+
class_dir = dir(WidgetApp)
|
|
31
|
+
|
|
32
|
+
assert "TITLE" in class_dir
|
|
33
|
+
assert "VERSION" in class_dir
|
|
34
|
+
assert "MODELS" in class_dir
|
|
35
|
+
assert WidgetApp.TITLE == "Widget App"
|
|
36
|
+
assert WidgetApp.VERSION == "1.0.0"
|
|
37
|
+
assert WidgetApp.MODELS == (Widget,)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tigrbl-tests
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Test suite and fixtures for the Tigrbl framework.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -30,6 +30,7 @@ Requires-Dist: python-dotenv
|
|
|
30
30
|
Requires-Dist: requests (>=2.32.3)
|
|
31
31
|
Requires-Dist: ruff (>=0.9.9)
|
|
32
32
|
Requires-Dist: tigrbl
|
|
33
|
+
Requires-Dist: tigrbl_client
|
|
33
34
|
Description-Content-Type: text/markdown
|
|
34
35
|
|
|
35
36
|

|
|
@@ -100,4 +101,18 @@ Use pytest selectors to focus on specific suites:
|
|
|
100
101
|
pytest standards/tigrbl_tests/tests/unit
|
|
101
102
|
```
|
|
102
103
|
|
|
104
|
+
## Examples Curriculum 📚
|
|
105
|
+
|
|
106
|
+
The `examples/` directory contains downstream-facing pytest lessons that
|
|
107
|
+
demonstrate how to implement Tigrbl in real applications. These lessons are
|
|
108
|
+
organized as a multi-module curriculum with uvicorn-backed usage examples and
|
|
109
|
+
system diagnostics validation. See the full curriculum plan for the learning
|
|
110
|
+
sequence and module descriptions.[^tigrbl-examples]
|
|
111
|
+
Run the examples from the `pkgs` directory:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
uv run --package tigrbl-tests --directory standards/tigrbl_tests pytest examples
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
[^tigrbl-examples]: examples/README.md
|
|
103
118
|
|
|
@@ -43,6 +43,13 @@ tests/i9n/test_schema_ctx_spec_integration.py,sha256=gQB0YdqjGfkoflcbzdkrVLouefh
|
|
|
43
43
|
tests/i9n/test_sqlite_attachments.py,sha256=Dvv2Exhbs7P47kOLQjg5TyruXYJqZIQdnqZvoitVy5Y,1216
|
|
44
44
|
tests/i9n/test_storage_spec_integration.py,sha256=hHxn0odPOLSIXY9Rx5ZwgOZS5DPFtutIhw5JYlEFmPg,3918
|
|
45
45
|
tests/i9n/test_symmetry_parity.py,sha256=7b2iVc0e7I84bOLgyWvFA3Zg-maGOGTLtTcX5gCX1Zg,919
|
|
46
|
+
tests/i9n/test_tigrbl_api_app_usage_uvicorn.py,sha256=FaE81foEFgZ8w9son4m4XSxYLV9-Ss034WkJjgpPlDU,2692
|
|
47
|
+
tests/i9n/test_tigrbl_api_usage_uvicorn.py,sha256=9ThMl7YsFgRZUU7Lk4R-1c5NNqn2YcDeOL6L9w37WZI,2933
|
|
48
|
+
tests/i9n/test_tigrbl_api_uvicorn.py,sha256=_3_z_OnKhL7JdSPzxCv7Bq0bFmm9GOyiAmp3TroRdm4,1764
|
|
49
|
+
tests/i9n/test_tigrbl_app_include_api_uvicorn.py,sha256=LzjwmchWqgHexYt7ebA7aPj1jM29J8q-lPCKoXp7dhg,4396
|
|
50
|
+
tests/i9n/test_tigrbl_app_multi_api_uvicorn.py,sha256=sFDkspBHhYSLfmMsXgzHKBIHFFpYuo7Znj0URr3bowE,2270
|
|
51
|
+
tests/i9n/test_tigrbl_app_usage_uvicorn.py,sha256=qUfSOk5ye509JwEJl9i6ViXtKA-LI0JAV8IdWCU_Mgw,2889
|
|
52
|
+
tests/i9n/test_tigrbl_app_uvicorn.py,sha256=a06Zypy1kA5glvyHb3iaBmBJso7WGoT0ZmwHu0Bv1iY,1572
|
|
46
53
|
tests/i9n/test_v3_bulk_rest_endpoints.py,sha256=3nNXJ6l3Ady4wi5UnEVmrw76crw9Ur7Ys53wq6RwBAI,3928
|
|
47
54
|
tests/i9n/test_v3_default_rest_ops.py,sha256=X_wJB7bmCaK6c9U6oPZlgQmRXdF2PeuYCqQYboJFxDo,4752
|
|
48
55
|
tests/i9n/test_v3_default_rpc_ops.py,sha256=mnDi5DNP6gKIi31Uin7rLlxvyY47u0U_2HvK61XqpB8,7552
|
|
@@ -112,7 +119,10 @@ tests/unit/test_hybrid_session_run_sync.py,sha256=6zidxhpUNfE81xtS5kzQYPn36SinMP
|
|
|
112
119
|
tests/unit/test_in_tx.py,sha256=Bzn5iTkJGUpXG8J0Uduj9b8OThgPGXl-jEKfmWyWkPY,549
|
|
113
120
|
tests/unit/test_include_model_columns_namespace.py,sha256=IQhuCZ168NanMHiTlhs_B1seQwYIkaq3WaBqiWkC67c,591
|
|
114
121
|
tests/unit/test_include_models_base_prefix.py,sha256=z8Rvl3bMjf8kMLpqLn7Ia08dR1KQORrHzBIrubyEq8s,813
|
|
122
|
+
tests/unit/test_initialize_async_task.py,sha256=Q2VNj6WR9DAN_niM3g1r-K8bDkRcFB4whrwG1gaviBg,560
|
|
115
123
|
tests/unit/test_initialize_cross_ddl.py,sha256=kaku7ZVX68rDAxNBbZRaVyXSiw4Op6Jb5ex-eNHKmjU,689
|
|
124
|
+
tests/unit/test_initialize_mixed_engines.py,sha256=kQPUAsfOUwcbz3FPMaMuDQpyBIG4q_DvuC9Vye7mIvo,1017
|
|
125
|
+
tests/unit/test_initialize_task_schedule.py,sha256=OLMjubgyOxglRv2vtB6gicEK3_6WuYasZRftIPJ5sMw,555
|
|
116
126
|
tests/unit/test_io_spec_attributes.py,sha256=Da9u7yjw-KVbHF1tpQhUvl7HSfooe9L1k5y-ceRVsaM,5662
|
|
117
127
|
tests/unit/test_iospec_attributes.py,sha256=Xl5jOHBVJH4gmZqznaVrO3trqnwWYtztwRIryAnKsJo,2254
|
|
118
128
|
tests/unit/test_iospec_effects.py,sha256=frPnZNX3_dVqmG_b4rUCrrJRzGQ6JzHKCZm0GsM-csA,5687
|
|
@@ -163,7 +173,7 @@ tests/unit/test_schemas_binding.py,sha256=TifSTTDJwN18ewb3dYTczjD7h5oWfLEh1MwPV_
|
|
|
163
173
|
tests/unit/test_security_per_route.py,sha256=b_6KKWDaTVWAO5XTfcNIG2pxMoNhZOcPBVKlz3Bz5KQ,1512
|
|
164
174
|
tests/unit/test_should_wire_canonical.py,sha256=ANlw1UFm77UI5LDaVWZZmBHSh1kUiJzY4hm1blLW158,1662
|
|
165
175
|
tests/unit/test_spec_api.py,sha256=LUiDDLmW9oBZQXvT-29FjX_ZHXy9FPN13-f4gQ6i5tI,1130
|
|
166
|
-
tests/unit/test_spec_app.py,sha256=
|
|
176
|
+
tests/unit/test_spec_app.py,sha256=fdEMeAlepypgmqCxsQawfEOMSE5d3fia1LOwsxC95t8,753
|
|
167
177
|
tests/unit/test_spec_column.py,sha256=ja3IDQi5U2WtTUHRd4deniyZ6EZkwz3mylKH8CLgf7I,867
|
|
168
178
|
tests/unit/test_spec_engine.py,sha256=D2pU-NCGMh0gsX-PhL5ZkJyTeO09NkATSK8WY-ynHaw,1938
|
|
169
179
|
tests/unit/test_spec_field.py,sha256=68X7qtA3H2P9LQKEJTW14YVPQJ-cENBrXyVmMBwj430,491
|
|
@@ -181,8 +191,19 @@ tests/unit/test_sys_tx_async_begin.py,sha256=q0apjmNnxZcGqCkpD8I0uPFsLPxJN_1ZBjF
|
|
|
181
191
|
tests/unit/test_sys_tx_begin.py,sha256=ElKZIxeK98O60Eo3uMGx8tuoP9nFlFYpYja5eLCears,1429
|
|
182
192
|
tests/unit/test_sys_tx_commit.py,sha256=HOTCtKusz7iGr-PkW_gcId1NzpGMRXBUxUydpd5PbHc,1813
|
|
183
193
|
tests/unit/test_table_base_exports.py,sha256=8GcLU0vY_j0_d5Y7PqQKMgz0L4rD3iPXMBn1u6dPz6E,666
|
|
184
|
-
tests/unit/test_table_collect_spec.py,sha256=
|
|
194
|
+
tests/unit/test_table_collect_spec.py,sha256=Gs_QnfKb9rYILHMv1q7w43gUvvS6UnpXy8vsI74IQTE,1050
|
|
185
195
|
tests/unit/test_table_columns_namespace.py,sha256=IVtpdg5dRHqyqc6WW0hndLJ7uNhnjivJ0GOJLebl_1w,581
|
|
196
|
+
tests/unit/test_table_namespace_init.py,sha256=53CnG1hMRUBD_KAU50FpKvUilk4QhBT0qbY2ECkP9Fc,945
|
|
197
|
+
tests/unit/test_table_namespace_isolation.py,sha256=6pWz1yLdrPNoOGzOjwe9_GBRHosrOaNSshRscq9ytIs,1394
|
|
198
|
+
tests/unit/test_tigrbl_api_app_configuration.py,sha256=aa0lXbJJnycUIZN3DGEgYmPIENRZi5-K-xHVEDiA6jQ,2269
|
|
199
|
+
tests/unit/test_tigrbl_api_app_instantiation.py,sha256=SlmotlemN4G4NNsY3UCXYF0shIClgSadbBmqTT2jbWM,1016
|
|
200
|
+
tests/unit/test_tigrbl_api_app_subclass_definition.py,sha256=yMoQZAmQqzK6V0fMUeYXGPx1nv7zFJ3wYc1E7-awY_I,886
|
|
201
|
+
tests/unit/test_tigrbl_api_configuration.py,sha256=O1J2FCbrr9acujY976gsGgpfRLoabfO_esYxwtcyRkU,1312
|
|
202
|
+
tests/unit/test_tigrbl_api_instantiation.py,sha256=w4edFjjIezW-cmBw_N4VHpLR0Z38zwRGvAzCMm5i4fw,960
|
|
203
|
+
tests/unit/test_tigrbl_api_subclass_definition.py,sha256=VL5aK7dgasH2TVlcKqoyEdmlNxuUbguJy1T8dqirR3Q,935
|
|
204
|
+
tests/unit/test_tigrbl_app_configuration.py,sha256=78ygfvj86Yv3YBOSnqBAm1fnPxNmTqg728owWnw_U2g,1303
|
|
205
|
+
tests/unit/test_tigrbl_app_instantiation.py,sha256=QyNzcwD7uuunrpjIcFMFuisPJo73D06yaDnVtEf2Hh0,960
|
|
206
|
+
tests/unit/test_tigrbl_app_subclass_definition.py,sha256=loeowe-FikGpGpiNIkBM8KgQgLSZv1mFCbut_stHFg4,935
|
|
186
207
|
tests/unit/test_v3_favicon_endpoint.py,sha256=mlquQ6ZF83JeJDdtxS2p7kTnBeeNdMNDWhfYlhFssjc,485
|
|
187
208
|
tests/unit/test_v3_healthz_endpoint.py,sha256=6WCjROm_3bQg5H__t7Z0BkVghHrpopgpvXTQ_iJKJpw,988
|
|
188
209
|
tests/unit/test_v3_op_alias.py,sha256=lSb8xpA9Asmaz7VefFDJLwbNBHP_mN4hSVJr1NVvOI4,2353
|
|
@@ -190,7 +211,7 @@ tests/unit/test_v3_op_ctx_attributes.py,sha256=ygRtYSYYtDFCTIRVHX2DFYTliP15NcmVi
|
|
|
190
211
|
tests/unit/test_v3_schemas_and_decorators.py,sha256=1wmg7pmzQNjjbQPRjO8gkCM6BQpMRbeO2jmtDMSYXLU,3818
|
|
191
212
|
tests/unit/test_v3_storage_spec_attributes.py,sha256=iXpjD4GlLddhJ_jC2veND2s6usXdOm-S9sKKfQl9XK0,6540
|
|
192
213
|
tests/unit/test_verbosity.py,sha256=92dIDc-LBIzjR1d90aL077R29rYJztibKYQGTjI6G68,1945
|
|
193
|
-
tigrbl_tests-0.3.
|
|
194
|
-
tigrbl_tests-0.3.
|
|
195
|
-
tigrbl_tests-0.3.
|
|
196
|
-
tigrbl_tests-0.3.
|
|
214
|
+
tigrbl_tests-0.3.3.dist-info/METADATA,sha256=8KuBCcu2jXkwVup247jACRJHzp_qO00-6LrQP6kB_4g,3887
|
|
215
|
+
tigrbl_tests-0.3.3.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
216
|
+
tigrbl_tests-0.3.3.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
|
|
217
|
+
tigrbl_tests-0.3.3.dist-info/RECORD,,
|
|
File without changes
|