tigrbl-tests 0.3.2.dev1__py3-none-any.whl → 0.3.3.dev1__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_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.dev1.dist-info → tigrbl_tests-0.3.3.dev1.dist-info}/METADATA +16 -1
- {tigrbl_tests-0.3.2.dev1.dist-info → tigrbl_tests-0.3.3.dev1.dist-info}/RECORD +24 -6
- {tigrbl_tests-0.3.2.dev1.dist-info → tigrbl_tests-0.3.3.dev1.dist-info}/WHEEL +0 -0
- {tigrbl_tests-0.3.2.dev1.dist-info → tigrbl_tests-0.3.3.dev1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
from fastapi import Security
|
|
5
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
6
|
+
|
|
7
|
+
from tigrbl import Base, TigrblApi, TigrblApp
|
|
8
|
+
from tigrbl.engine.shortcuts import mem
|
|
9
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
+
from tigrbl.specs import F, IO, S, acol
|
|
11
|
+
from tigrbl.types import Mapped, String
|
|
12
|
+
|
|
13
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bearer = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def auth_dependency(
|
|
20
|
+
credentials: HTTPAuthorizationCredentials = Security(bearer),
|
|
21
|
+
) -> HTTPAuthorizationCredentials:
|
|
22
|
+
return credentials
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Kappa(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "kappa_api_app_usage"
|
|
27
|
+
__allow_unmapped__ = True
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(type_=String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class KappaApi(TigrblApi):
|
|
39
|
+
MODELS = (Kappa,)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@pytest_asyncio.fixture()
|
|
43
|
+
async def running_api_app():
|
|
44
|
+
api = KappaApi(engine=mem(async_=False))
|
|
45
|
+
api.set_auth(authn=auth_dependency, allow_anon=False)
|
|
46
|
+
api.include_models([Kappa])
|
|
47
|
+
api.initialize()
|
|
48
|
+
|
|
49
|
+
class KappaApp(TigrblApp):
|
|
50
|
+
APIS = (api,)
|
|
51
|
+
|
|
52
|
+
app = KappaApp(engine=mem(async_=False))
|
|
53
|
+
app.include_router(api)
|
|
54
|
+
|
|
55
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
56
|
+
try:
|
|
57
|
+
yield base_url
|
|
58
|
+
finally:
|
|
59
|
+
await stop_uvicorn_server(server, task)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@pytest.mark.i9n
|
|
63
|
+
@pytest.mark.asyncio
|
|
64
|
+
async def test_tigrbl_api_app_deploys_and_serves_openapi(running_api_app) -> None:
|
|
65
|
+
base_url = running_api_app
|
|
66
|
+
|
|
67
|
+
async with httpx.AsyncClient() as client:
|
|
68
|
+
openapi_resp = await client.get(f"{base_url}/openapi.json")
|
|
69
|
+
|
|
70
|
+
assert openapi_resp.status_code == 200
|
|
71
|
+
openapi = openapi_resp.json()
|
|
72
|
+
paths = openapi["paths"]
|
|
73
|
+
|
|
74
|
+
assert "/kappa" in paths
|
|
75
|
+
assert "/kappa/{item_id}" in paths
|
|
76
|
+
assert {"get", "post", "delete"}.issubset(paths["/kappa"])
|
|
77
|
+
assert {"get", "patch", "put", "delete"}.issubset(paths["/kappa/{item_id}"])
|
|
78
|
+
|
|
79
|
+
security_schemes = openapi.get("components", {}).get("securitySchemes", {})
|
|
80
|
+
assert "HTTPBearer" in security_schemes
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.i9n
|
|
84
|
+
@pytest.mark.asyncio
|
|
85
|
+
async def test_tigrbl_api_app_handles_authenticated_request(running_api_app) -> None:
|
|
86
|
+
base_url = running_api_app
|
|
87
|
+
headers = {"Authorization": "Bearer demo"}
|
|
88
|
+
|
|
89
|
+
async with httpx.AsyncClient() as client:
|
|
90
|
+
resp = await client.post(
|
|
91
|
+
f"{base_url}/kappa",
|
|
92
|
+
json={"name": "Kappa"},
|
|
93
|
+
headers=headers,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
assert resp.status_code == 201
|
|
97
|
+
payload = resp.json()
|
|
98
|
+
assert payload["name"] == "Kappa"
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
from fastapi import Security
|
|
5
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
6
|
+
|
|
7
|
+
from tigrbl import Base, TigrblApi
|
|
8
|
+
from tigrbl.engine.shortcuts import mem
|
|
9
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
+
from tigrbl.specs import F, IO, S, acol
|
|
11
|
+
from tigrbl.types import App, Mapped, String
|
|
12
|
+
|
|
13
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bearer = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def auth_dependency(
|
|
20
|
+
credentials: HTTPAuthorizationCredentials = Security(bearer),
|
|
21
|
+
) -> HTTPAuthorizationCredentials:
|
|
22
|
+
return credentials
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Alpha(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "alpha_api_usage"
|
|
27
|
+
__allow_unmapped__ = True
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(type_=String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Beta(Base, GUIDPk):
|
|
39
|
+
__tablename__ = "beta_api_usage"
|
|
40
|
+
__allow_unmapped__ = True
|
|
41
|
+
|
|
42
|
+
name: Mapped[str] = acol(
|
|
43
|
+
storage=S(type_=String, nullable=False),
|
|
44
|
+
field=F(py_type=str),
|
|
45
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest_asyncio.fixture()
|
|
52
|
+
async def running_api():
|
|
53
|
+
app = App()
|
|
54
|
+
api = TigrblApi(engine=mem(async_=False))
|
|
55
|
+
api.set_auth(authn=auth_dependency, allow_anon=False)
|
|
56
|
+
api.include_models([Alpha, Beta])
|
|
57
|
+
api.initialize()
|
|
58
|
+
app.include_router(api)
|
|
59
|
+
|
|
60
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
61
|
+
try:
|
|
62
|
+
yield base_url
|
|
63
|
+
finally:
|
|
64
|
+
await stop_uvicorn_server(server, task)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest.mark.i9n
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
async def test_tigrbl_api_deploys_and_serves_openapi(running_api) -> None:
|
|
70
|
+
base_url = running_api
|
|
71
|
+
|
|
72
|
+
async with httpx.AsyncClient() as client:
|
|
73
|
+
openapi_resp = await client.get(f"{base_url}/openapi.json")
|
|
74
|
+
|
|
75
|
+
assert openapi_resp.status_code == 200
|
|
76
|
+
openapi = openapi_resp.json()
|
|
77
|
+
paths = openapi["paths"]
|
|
78
|
+
|
|
79
|
+
assert "/alpha" in paths
|
|
80
|
+
assert "/alpha/{item_id}" in paths
|
|
81
|
+
assert "/beta" in paths
|
|
82
|
+
assert "/beta/{item_id}" in paths
|
|
83
|
+
assert {"get", "post", "delete"}.issubset(paths["/alpha"])
|
|
84
|
+
assert {"get", "patch", "put", "delete"}.issubset(paths["/alpha/{item_id}"])
|
|
85
|
+
|
|
86
|
+
security_schemes = openapi.get("components", {}).get("securitySchemes", {})
|
|
87
|
+
assert "HTTPBearer" in security_schemes
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@pytest.mark.i9n
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_tigrbl_api_handles_authenticated_request(running_api) -> None:
|
|
93
|
+
base_url = running_api
|
|
94
|
+
headers = {"Authorization": "Bearer demo"}
|
|
95
|
+
|
|
96
|
+
async with httpx.AsyncClient() as client:
|
|
97
|
+
resp = await client.post(
|
|
98
|
+
f"{base_url}/alpha",
|
|
99
|
+
json={"name": "Alpha"},
|
|
100
|
+
headers=headers,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
assert resp.status_code == 201
|
|
104
|
+
payload = resp.json()
|
|
105
|
+
assert payload["name"] == "Alpha"
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, TigrblApi
|
|
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 App, Mapped, String
|
|
10
|
+
|
|
11
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Gadget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "gadgets_api"
|
|
16
|
+
__resource__ = "gadget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest_asyncio.fixture()
|
|
26
|
+
async def running_api_app():
|
|
27
|
+
api = TigrblApi(
|
|
28
|
+
engine=mem(async_=False),
|
|
29
|
+
models=[Gadget],
|
|
30
|
+
prefix="/gadgets",
|
|
31
|
+
system_prefix="/diagnostics",
|
|
32
|
+
)
|
|
33
|
+
await api.initialize()
|
|
34
|
+
|
|
35
|
+
app = App()
|
|
36
|
+
app.include_router(api.router)
|
|
37
|
+
api.attach_diagnostics(app=app)
|
|
38
|
+
|
|
39
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
40
|
+
try:
|
|
41
|
+
yield base_url
|
|
42
|
+
finally:
|
|
43
|
+
await stop_uvicorn_server(server, task)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@pytest.mark.i9n
|
|
47
|
+
@pytest.mark.asyncio
|
|
48
|
+
async def test_tigrbl_api_create_gadget(running_api_app):
|
|
49
|
+
async with httpx.AsyncClient() as client:
|
|
50
|
+
response = await client.post(
|
|
51
|
+
f"{running_api_app}/gadgets/gadget", json={"name": "gyro"}
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
assert response.status_code == 201
|
|
55
|
+
payload = response.json()
|
|
56
|
+
assert payload["name"] == "gyro"
|
|
57
|
+
assert "id" in payload
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.mark.i9n
|
|
61
|
+
@pytest.mark.asyncio
|
|
62
|
+
async def test_tigrbl_api_healthz(running_api_app):
|
|
63
|
+
async with httpx.AsyncClient() as client:
|
|
64
|
+
response = await client.get(f"{running_api_app}/diagnostics/healthz")
|
|
65
|
+
|
|
66
|
+
assert response.status_code == 200
|
|
67
|
+
assert response.json()["ok"] is True
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
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
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AlphaWidget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "alpha_widgets_alt"
|
|
16
|
+
__resource__ = "alpha-widget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BetaWidget(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "beta_widgets_alt"
|
|
27
|
+
__resource__ = "beta-widget"
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ZetaWidget(Base, GUIDPk):
|
|
37
|
+
__tablename__ = "zeta_widgets_alt"
|
|
38
|
+
__resource__ = "zeta-widget"
|
|
39
|
+
|
|
40
|
+
name: Mapped[str] = acol(
|
|
41
|
+
storage=S(String, nullable=False),
|
|
42
|
+
field=F(py_type=str),
|
|
43
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@pytest_asyncio.fixture()
|
|
48
|
+
async def running_app_with_apis():
|
|
49
|
+
engine = mem(async_=False)
|
|
50
|
+
alpha_api = TigrblApi(engine=engine, models=[AlphaWidget], prefix="/alpha")
|
|
51
|
+
beta_api = TigrblApi(engine=engine)
|
|
52
|
+
beta_api.include_model(BetaWidget)
|
|
53
|
+
zeta_api = TigrblApi(engine=engine)
|
|
54
|
+
zeta_api.include_model(ZetaWidget)
|
|
55
|
+
|
|
56
|
+
app = TigrblApp(engine=engine, apis=[alpha_api, (zeta_api, "/zeta")])
|
|
57
|
+
app.include_api(beta_api, prefix="/beta")
|
|
58
|
+
await app.initialize()
|
|
59
|
+
|
|
60
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
61
|
+
try:
|
|
62
|
+
yield base_url
|
|
63
|
+
finally:
|
|
64
|
+
await stop_uvicorn_server(server, task)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@pytest_asyncio.fixture()
|
|
68
|
+
async def running_app_with_api_router():
|
|
69
|
+
engine = mem(async_=False)
|
|
70
|
+
alpha_api = TigrblApi(engine=engine)
|
|
71
|
+
alpha_api.include_model(AlphaWidget)
|
|
72
|
+
beta_api = TigrblApi(engine=engine)
|
|
73
|
+
beta_api.include_model(BetaWidget)
|
|
74
|
+
|
|
75
|
+
app = TigrblApp(engine=engine, apis=[alpha_api])
|
|
76
|
+
app.include_api(beta_api, prefix="/beta")
|
|
77
|
+
app.include_api(alpha_api.router, prefix="/alpha")
|
|
78
|
+
await app.initialize()
|
|
79
|
+
|
|
80
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
81
|
+
try:
|
|
82
|
+
yield base_url
|
|
83
|
+
finally:
|
|
84
|
+
await stop_uvicorn_server(server, task)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.mark.i9n
|
|
88
|
+
@pytest.mark.asyncio
|
|
89
|
+
async def test_tigrbl_app_api_list_alpha(running_app_with_apis):
|
|
90
|
+
async with httpx.AsyncClient() as client:
|
|
91
|
+
response = await client.post(
|
|
92
|
+
f"{running_app_with_apis}/alpha/alpha-widget", json={"name": "ace"}
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
assert response.status_code == 201
|
|
96
|
+
payload = response.json()
|
|
97
|
+
assert payload["name"] == "ace"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.mark.i9n
|
|
101
|
+
@pytest.mark.asyncio
|
|
102
|
+
async def test_tigrbl_app_include_api_beta(running_app_with_apis):
|
|
103
|
+
async with httpx.AsyncClient() as client:
|
|
104
|
+
response = await client.post(
|
|
105
|
+
f"{running_app_with_apis}/beta/beta-widget", json={"name": "bolt"}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
assert response.status_code == 201
|
|
109
|
+
payload = response.json()
|
|
110
|
+
assert payload["name"] == "bolt"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.mark.i9n
|
|
114
|
+
@pytest.mark.asyncio
|
|
115
|
+
async def test_tigrbl_app_api_list_zeta(running_app_with_apis):
|
|
116
|
+
async with httpx.AsyncClient() as client:
|
|
117
|
+
response = await client.post(
|
|
118
|
+
f"{running_app_with_apis}/zeta/zeta-widget", json={"name": "zen"}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
assert response.status_code == 201
|
|
122
|
+
payload = response.json()
|
|
123
|
+
assert payload["name"] == "zen"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.i9n
|
|
127
|
+
@pytest.mark.asyncio
|
|
128
|
+
async def test_tigrbl_app_include_api_router_alpha(running_app_with_api_router):
|
|
129
|
+
async with httpx.AsyncClient() as client:
|
|
130
|
+
response = await client.post(
|
|
131
|
+
f"{running_app_with_api_router}/alpha/alpha-widget", json={"name": "ace"}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
assert response.status_code == 201
|
|
135
|
+
payload = response.json()
|
|
136
|
+
assert payload["name"] == "ace"
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.i9n
|
|
140
|
+
@pytest.mark.asyncio
|
|
141
|
+
async def test_tigrbl_app_include_api_router_beta(running_app_with_api_router):
|
|
142
|
+
async with httpx.AsyncClient() as client:
|
|
143
|
+
response = await client.post(
|
|
144
|
+
f"{running_app_with_api_router}/beta/beta-widget", json={"name": "bolt"}
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
assert response.status_code == 201
|
|
148
|
+
payload = response.json()
|
|
149
|
+
assert payload["name"] == "bolt"
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
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
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AlphaWidget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "alpha_widgets"
|
|
16
|
+
__resource__ = "alpha-widget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class BetaWidget(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "beta_widgets"
|
|
27
|
+
__resource__ = "beta-widget"
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest_asyncio.fixture()
|
|
37
|
+
async def running_multi_api_app():
|
|
38
|
+
engine = mem(async_=False)
|
|
39
|
+
alpha_api = TigrblApi(engine=engine)
|
|
40
|
+
alpha_api.include_model(AlphaWidget)
|
|
41
|
+
|
|
42
|
+
beta_api = TigrblApi(engine=engine)
|
|
43
|
+
beta_api.include_model(BetaWidget)
|
|
44
|
+
|
|
45
|
+
app = TigrblApp(engine=engine, apis=[alpha_api])
|
|
46
|
+
app.include_router(beta_api, prefix="/beta")
|
|
47
|
+
app.include_router(alpha_api.router, prefix="/alpha")
|
|
48
|
+
await app.initialize()
|
|
49
|
+
|
|
50
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
51
|
+
try:
|
|
52
|
+
yield base_url
|
|
53
|
+
finally:
|
|
54
|
+
await stop_uvicorn_server(server, task)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.i9n
|
|
58
|
+
@pytest.mark.asyncio
|
|
59
|
+
async def test_tigrbl_app_routes_alpha_api(running_multi_api_app):
|
|
60
|
+
async with httpx.AsyncClient() as client:
|
|
61
|
+
response = await client.post(
|
|
62
|
+
f"{running_multi_api_app}/alpha/alpha-widget", json={"name": "ace"}
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
assert response.status_code == 201
|
|
66
|
+
payload = response.json()
|
|
67
|
+
assert payload["name"] == "ace"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.i9n
|
|
71
|
+
@pytest.mark.asyncio
|
|
72
|
+
async def test_tigrbl_app_routes_beta_api(running_multi_api_app):
|
|
73
|
+
async with httpx.AsyncClient() as client:
|
|
74
|
+
response = await client.post(
|
|
75
|
+
f"{running_multi_api_app}/beta/beta-widget", json={"name": "bolt"}
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
assert response.status_code == 201
|
|
79
|
+
payload = response.json()
|
|
80
|
+
assert payload["name"] == "bolt"
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
from fastapi import Security
|
|
5
|
+
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
|
6
|
+
|
|
7
|
+
from tigrbl import Base, TigrblApp
|
|
8
|
+
from tigrbl.engine.shortcuts import mem
|
|
9
|
+
from tigrbl.orm.mixins import GUIDPk
|
|
10
|
+
from tigrbl.specs import F, IO, S, acol
|
|
11
|
+
from tigrbl.types import Mapped, String
|
|
12
|
+
|
|
13
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
bearer = HTTPBearer()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def auth_dependency(
|
|
20
|
+
credentials: HTTPAuthorizationCredentials = Security(bearer),
|
|
21
|
+
) -> HTTPAuthorizationCredentials:
|
|
22
|
+
return credentials
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Gamma(Base, GUIDPk):
|
|
26
|
+
__tablename__ = "gamma_app_usage"
|
|
27
|
+
__allow_unmapped__ = True
|
|
28
|
+
|
|
29
|
+
name: Mapped[str] = acol(
|
|
30
|
+
storage=S(type_=String, nullable=False),
|
|
31
|
+
field=F(py_type=str),
|
|
32
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Delta(Base, GUIDPk):
|
|
39
|
+
__tablename__ = "delta_app_usage"
|
|
40
|
+
__allow_unmapped__ = True
|
|
41
|
+
|
|
42
|
+
name: Mapped[str] = acol(
|
|
43
|
+
storage=S(type_=String, nullable=False),
|
|
44
|
+
field=F(py_type=str),
|
|
45
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
__tigrbl_cols__ = {"id": GUIDPk.id, "name": name}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest_asyncio.fixture()
|
|
52
|
+
async def running_app():
|
|
53
|
+
app = TigrblApp(engine=mem(async_=False))
|
|
54
|
+
app.set_auth(authn=auth_dependency, allow_anon=False)
|
|
55
|
+
app.include_models([Gamma, Delta])
|
|
56
|
+
app.initialize()
|
|
57
|
+
|
|
58
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
59
|
+
try:
|
|
60
|
+
yield base_url
|
|
61
|
+
finally:
|
|
62
|
+
await stop_uvicorn_server(server, task)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.mark.i9n
|
|
66
|
+
@pytest.mark.asyncio
|
|
67
|
+
async def test_tigrbl_app_deploys_and_serves_openapi(running_app) -> None:
|
|
68
|
+
base_url = running_app
|
|
69
|
+
|
|
70
|
+
async with httpx.AsyncClient() as client:
|
|
71
|
+
openapi_resp = await client.get(f"{base_url}/openapi.json")
|
|
72
|
+
|
|
73
|
+
assert openapi_resp.status_code == 200
|
|
74
|
+
openapi = openapi_resp.json()
|
|
75
|
+
paths = openapi["paths"]
|
|
76
|
+
|
|
77
|
+
assert "/gamma" in paths
|
|
78
|
+
assert "/gamma/{item_id}" in paths
|
|
79
|
+
assert "/delta" in paths
|
|
80
|
+
assert "/delta/{item_id}" in paths
|
|
81
|
+
assert {"get", "post", "delete"}.issubset(paths["/gamma"])
|
|
82
|
+
assert {"get", "patch", "put", "delete"}.issubset(paths["/gamma/{item_id}"])
|
|
83
|
+
|
|
84
|
+
security_schemes = openapi.get("components", {}).get("securitySchemes", {})
|
|
85
|
+
assert "HTTPBearer" in security_schemes
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@pytest.mark.i9n
|
|
89
|
+
@pytest.mark.asyncio
|
|
90
|
+
async def test_tigrbl_app_handles_authenticated_request(running_app) -> None:
|
|
91
|
+
base_url = running_app
|
|
92
|
+
headers = {"Authorization": "Bearer demo"}
|
|
93
|
+
|
|
94
|
+
async with httpx.AsyncClient() as client:
|
|
95
|
+
resp = await client.post(
|
|
96
|
+
f"{base_url}/gamma",
|
|
97
|
+
json={"name": "Gamma"},
|
|
98
|
+
headers=headers,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert resp.status_code == 201
|
|
102
|
+
payload = resp.json()
|
|
103
|
+
assert payload["name"] == "Gamma"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import httpx
|
|
2
|
+
import pytest
|
|
3
|
+
import pytest_asyncio
|
|
4
|
+
|
|
5
|
+
from tigrbl import Base, 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
|
+
from .uvicorn_utils import run_uvicorn_in_task, stop_uvicorn_server
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Widget(Base, GUIDPk):
|
|
15
|
+
__tablename__ = "widgets_app"
|
|
16
|
+
__resource__ = "widget"
|
|
17
|
+
|
|
18
|
+
name: Mapped[str] = acol(
|
|
19
|
+
storage=S(String, nullable=False),
|
|
20
|
+
field=F(py_type=str),
|
|
21
|
+
io=IO(in_verbs=("create",), out_verbs=("create", "read", "list")),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest_asyncio.fixture()
|
|
26
|
+
async def running_app():
|
|
27
|
+
app = TigrblApp(engine=mem(async_=False))
|
|
28
|
+
app.include_model(Widget)
|
|
29
|
+
app.attach_diagnostics()
|
|
30
|
+
await app.initialize()
|
|
31
|
+
|
|
32
|
+
base_url, server, task = await run_uvicorn_in_task(app)
|
|
33
|
+
try:
|
|
34
|
+
yield base_url
|
|
35
|
+
finally:
|
|
36
|
+
await stop_uvicorn_server(server, task)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@pytest.mark.i9n
|
|
40
|
+
@pytest.mark.asyncio
|
|
41
|
+
async def test_tigrbl_app_create_widget(running_app):
|
|
42
|
+
async with httpx.AsyncClient() as client:
|
|
43
|
+
response = await client.post(f"{running_app}/widget", json={"name": "alpha"})
|
|
44
|
+
|
|
45
|
+
assert response.status_code == 201
|
|
46
|
+
payload = response.json()
|
|
47
|
+
assert payload["name"] == "alpha"
|
|
48
|
+
assert "id" in payload
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.i9n
|
|
52
|
+
@pytest.mark.asyncio
|
|
53
|
+
async def test_tigrbl_app_healthz(running_app):
|
|
54
|
+
async with httpx.AsyncClient() as client:
|
|
55
|
+
response = await client.get(f"{running_app}/system/healthz")
|
|
56
|
+
|
|
57
|
+
assert response.status_code == 200
|
|
58
|
+
assert response.json()["ok"] 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.dev1
|
|
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
|
|
@@ -163,7 +170,7 @@ tests/unit/test_schemas_binding.py,sha256=TifSTTDJwN18ewb3dYTczjD7h5oWfLEh1MwPV_
|
|
|
163
170
|
tests/unit/test_security_per_route.py,sha256=b_6KKWDaTVWAO5XTfcNIG2pxMoNhZOcPBVKlz3Bz5KQ,1512
|
|
164
171
|
tests/unit/test_should_wire_canonical.py,sha256=ANlw1UFm77UI5LDaVWZZmBHSh1kUiJzY4hm1blLW158,1662
|
|
165
172
|
tests/unit/test_spec_api.py,sha256=LUiDDLmW9oBZQXvT-29FjX_ZHXy9FPN13-f4gQ6i5tI,1130
|
|
166
|
-
tests/unit/test_spec_app.py,sha256=
|
|
173
|
+
tests/unit/test_spec_app.py,sha256=fdEMeAlepypgmqCxsQawfEOMSE5d3fia1LOwsxC95t8,753
|
|
167
174
|
tests/unit/test_spec_column.py,sha256=ja3IDQi5U2WtTUHRd4deniyZ6EZkwz3mylKH8CLgf7I,867
|
|
168
175
|
tests/unit/test_spec_engine.py,sha256=D2pU-NCGMh0gsX-PhL5ZkJyTeO09NkATSK8WY-ynHaw,1938
|
|
169
176
|
tests/unit/test_spec_field.py,sha256=68X7qtA3H2P9LQKEJTW14YVPQJ-cENBrXyVmMBwj430,491
|
|
@@ -181,8 +188,19 @@ tests/unit/test_sys_tx_async_begin.py,sha256=q0apjmNnxZcGqCkpD8I0uPFsLPxJN_1ZBjF
|
|
|
181
188
|
tests/unit/test_sys_tx_begin.py,sha256=ElKZIxeK98O60Eo3uMGx8tuoP9nFlFYpYja5eLCears,1429
|
|
182
189
|
tests/unit/test_sys_tx_commit.py,sha256=HOTCtKusz7iGr-PkW_gcId1NzpGMRXBUxUydpd5PbHc,1813
|
|
183
190
|
tests/unit/test_table_base_exports.py,sha256=8GcLU0vY_j0_d5Y7PqQKMgz0L4rD3iPXMBn1u6dPz6E,666
|
|
184
|
-
tests/unit/test_table_collect_spec.py,sha256=
|
|
191
|
+
tests/unit/test_table_collect_spec.py,sha256=Gs_QnfKb9rYILHMv1q7w43gUvvS6UnpXy8vsI74IQTE,1050
|
|
185
192
|
tests/unit/test_table_columns_namespace.py,sha256=IVtpdg5dRHqyqc6WW0hndLJ7uNhnjivJ0GOJLebl_1w,581
|
|
193
|
+
tests/unit/test_table_namespace_init.py,sha256=53CnG1hMRUBD_KAU50FpKvUilk4QhBT0qbY2ECkP9Fc,945
|
|
194
|
+
tests/unit/test_table_namespace_isolation.py,sha256=6pWz1yLdrPNoOGzOjwe9_GBRHosrOaNSshRscq9ytIs,1394
|
|
195
|
+
tests/unit/test_tigrbl_api_app_configuration.py,sha256=aa0lXbJJnycUIZN3DGEgYmPIENRZi5-K-xHVEDiA6jQ,2269
|
|
196
|
+
tests/unit/test_tigrbl_api_app_instantiation.py,sha256=SlmotlemN4G4NNsY3UCXYF0shIClgSadbBmqTT2jbWM,1016
|
|
197
|
+
tests/unit/test_tigrbl_api_app_subclass_definition.py,sha256=yMoQZAmQqzK6V0fMUeYXGPx1nv7zFJ3wYc1E7-awY_I,886
|
|
198
|
+
tests/unit/test_tigrbl_api_configuration.py,sha256=O1J2FCbrr9acujY976gsGgpfRLoabfO_esYxwtcyRkU,1312
|
|
199
|
+
tests/unit/test_tigrbl_api_instantiation.py,sha256=w4edFjjIezW-cmBw_N4VHpLR0Z38zwRGvAzCMm5i4fw,960
|
|
200
|
+
tests/unit/test_tigrbl_api_subclass_definition.py,sha256=VL5aK7dgasH2TVlcKqoyEdmlNxuUbguJy1T8dqirR3Q,935
|
|
201
|
+
tests/unit/test_tigrbl_app_configuration.py,sha256=78ygfvj86Yv3YBOSnqBAm1fnPxNmTqg728owWnw_U2g,1303
|
|
202
|
+
tests/unit/test_tigrbl_app_instantiation.py,sha256=QyNzcwD7uuunrpjIcFMFuisPJo73D06yaDnVtEf2Hh0,960
|
|
203
|
+
tests/unit/test_tigrbl_app_subclass_definition.py,sha256=loeowe-FikGpGpiNIkBM8KgQgLSZv1mFCbut_stHFg4,935
|
|
186
204
|
tests/unit/test_v3_favicon_endpoint.py,sha256=mlquQ6ZF83JeJDdtxS2p7kTnBeeNdMNDWhfYlhFssjc,485
|
|
187
205
|
tests/unit/test_v3_healthz_endpoint.py,sha256=6WCjROm_3bQg5H__t7Z0BkVghHrpopgpvXTQ_iJKJpw,988
|
|
188
206
|
tests/unit/test_v3_op_alias.py,sha256=lSb8xpA9Asmaz7VefFDJLwbNBHP_mN4hSVJr1NVvOI4,2353
|
|
@@ -190,7 +208,7 @@ tests/unit/test_v3_op_ctx_attributes.py,sha256=ygRtYSYYtDFCTIRVHX2DFYTliP15NcmVi
|
|
|
190
208
|
tests/unit/test_v3_schemas_and_decorators.py,sha256=1wmg7pmzQNjjbQPRjO8gkCM6BQpMRbeO2jmtDMSYXLU,3818
|
|
191
209
|
tests/unit/test_v3_storage_spec_attributes.py,sha256=iXpjD4GlLddhJ_jC2veND2s6usXdOm-S9sKKfQl9XK0,6540
|
|
192
210
|
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.
|
|
211
|
+
tigrbl_tests-0.3.3.dev1.dist-info/METADATA,sha256=mE6ccLvpw3lzMwkPIGP6iXuag8tRJCy3OkkXdpo7iqg,3892
|
|
212
|
+
tigrbl_tests-0.3.3.dev1.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
|
|
213
|
+
tigrbl_tests-0.3.3.dev1.dist-info/licenses/LICENSE,sha256=djUXOlCxLVszShEpZXshZ7v33G-2qIC_j9KXpWKZSzQ,11359
|
|
214
|
+
tigrbl_tests-0.3.3.dev1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|