tigrbl_client 0.2.2.dev27__tar.gz → 0.2.12.dev1__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.
- tigrbl_client-0.2.12.dev1/PKG-INFO +49 -0
- tigrbl_client-0.2.12.dev1/README.md +23 -0
- {tigrbl_client-0.2.2.dev27 → tigrbl_client-0.2.12.dev1}/pyproject.toml +4 -9
- {tigrbl_client-0.2.2.dev27 → tigrbl_client-0.2.12.dev1}/tigrbl_client/__init__.py +3 -3
- tigrbl_client-0.2.12.dev1/tigrbl_client/_nested_crud.py +87 -0
- tigrbl_client-0.2.2.dev27/PKG-INFO +0 -80
- tigrbl_client-0.2.2.dev27/README.md +0 -54
- tigrbl_client-0.2.2.dev27/tigrbl_client/_nested_crud.py +0 -50
- {tigrbl_client-0.2.2.dev27 → tigrbl_client-0.2.12.dev1}/LICENSE +0 -0
- {tigrbl_client-0.2.2.dev27 → tigrbl_client-0.2.12.dev1}/tigrbl_client/_crud.py +0 -0
- {tigrbl_client-0.2.2.dev27 → tigrbl_client-0.2.12.dev1}/tigrbl_client/_rpc.py +0 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tigrbl_client
|
|
3
|
+
Version: 0.2.12.dev1
|
|
4
|
+
Summary: A client for Tigrbl servers by Swarmauri.
|
|
5
|
+
License-Expression: Apache-2.0
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: tigrbl,sdk,standards,client
|
|
8
|
+
Author: Jacob Stewart
|
|
9
|
+
Author-email: jacob@swarmauri.com
|
|
10
|
+
Requires-Python: >=3.10,<3.14
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Requires-Dist: fastapi (>=0.100.0)
|
|
20
|
+
Requires-Dist: pydantic (>=2.0.0)
|
|
21
|
+
Requires-Dist: swarmauri_base
|
|
22
|
+
Requires-Dist: swarmauri_core
|
|
23
|
+
Requires-Dist: swarmauri_standard
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
# tigrbl_client
|
|
27
|
+
|
|
28
|
+
This file is a package-local distribution entry point.
|
|
29
|
+
It is not the authoritative location for repository governance, current target status, current state reporting, certification claims, or release evidence.
|
|
30
|
+
|
|
31
|
+
## Canonical repository docs
|
|
32
|
+
|
|
33
|
+
- `README.md`
|
|
34
|
+
- `docs/README.md`
|
|
35
|
+
- `docs/conformance/CURRENT_TARGET.md`
|
|
36
|
+
- `docs/conformance/CURRENT_STATE.md`
|
|
37
|
+
- `docs/conformance/NEXT_STEPS.md`
|
|
38
|
+
- `docs/governance/DOC_POINTERS.md`
|
|
39
|
+
- `docs/developer/PACKAGE_CATALOG.md`
|
|
40
|
+
- `docs/developer/PACKAGE_LAYOUT.md`
|
|
41
|
+
|
|
42
|
+
## Package identity
|
|
43
|
+
|
|
44
|
+
- workspace path: `pkgs/core/tigrbl_client`
|
|
45
|
+
- workspace class: core Python package
|
|
46
|
+
- implementation layout: `tigrbl_client/`
|
|
47
|
+
|
|
48
|
+
Long-form repository documentation is governed from `docs/`.
|
|
49
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# tigrbl_client
|
|
2
|
+
|
|
3
|
+
This file is a package-local distribution entry point.
|
|
4
|
+
It is not the authoritative location for repository governance, current target status, current state reporting, certification claims, or release evidence.
|
|
5
|
+
|
|
6
|
+
## Canonical repository docs
|
|
7
|
+
|
|
8
|
+
- `README.md`
|
|
9
|
+
- `docs/README.md`
|
|
10
|
+
- `docs/conformance/CURRENT_TARGET.md`
|
|
11
|
+
- `docs/conformance/CURRENT_STATE.md`
|
|
12
|
+
- `docs/conformance/NEXT_STEPS.md`
|
|
13
|
+
- `docs/governance/DOC_POINTERS.md`
|
|
14
|
+
- `docs/developer/PACKAGE_CATALOG.md`
|
|
15
|
+
- `docs/developer/PACKAGE_LAYOUT.md`
|
|
16
|
+
|
|
17
|
+
## Package identity
|
|
18
|
+
|
|
19
|
+
- workspace path: `pkgs/core/tigrbl_client`
|
|
20
|
+
- workspace class: core Python package
|
|
21
|
+
- implementation layout: `tigrbl_client/`
|
|
22
|
+
|
|
23
|
+
Long-form repository documentation is governed from `docs/`.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tigrbl_client"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.12.dev1"
|
|
4
4
|
description = "A client for Tigrbl servers by Swarmauri."
|
|
5
5
|
license = "Apache-2.0"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
repository = "http://github.com/swarmauri/swarmauri-sdk"
|
|
8
|
-
requires-python = ">=3.10,<3.
|
|
8
|
+
requires-python = ">=3.10,<3.14"
|
|
9
9
|
classifiers = [
|
|
10
|
-
"License :: OSI Approved :: Apache Software License",
|
|
11
10
|
"Development Status :: 3 - Alpha",
|
|
12
11
|
"Programming Language :: Python :: 3.10",
|
|
13
12
|
"Programming Language :: Python :: 3.11",
|
|
14
13
|
"Programming Language :: Python :: 3.12",
|
|
14
|
+
"Programming Language :: Python :: 3.13",
|
|
15
15
|
"Programming Language :: Python",
|
|
16
16
|
"Programming Language :: Python :: 3",
|
|
17
17
|
"Programming Language :: Python :: 3 :: Only",
|
|
@@ -31,11 +31,6 @@ keywords = [
|
|
|
31
31
|
'client',
|
|
32
32
|
]
|
|
33
33
|
|
|
34
|
-
[tool.uv.sources]
|
|
35
|
-
swarmauri_core = { workspace = true }
|
|
36
|
-
swarmauri_base = { workspace = true }
|
|
37
|
-
swarmauri_standard = { workspace = true }
|
|
38
|
-
|
|
39
34
|
[tool.pytest.ini_options]
|
|
40
35
|
norecursedirs = ["combined", "scripts"]
|
|
41
36
|
markers = [
|
|
@@ -72,4 +67,4 @@ dev = [
|
|
|
72
67
|
"pytest-timeout>=2.3.1",
|
|
73
68
|
"ruff>=0.9.9",
|
|
74
69
|
"pytest-benchmark>=4.0.0",
|
|
75
|
-
]
|
|
70
|
+
]
|
|
@@ -12,7 +12,7 @@ T = TypeVar("T")
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
# Re-export the Schema protocol for public use
|
|
15
|
-
__all__ = ["TigrblClient", "_Schema"]
|
|
15
|
+
__all__ = ["TigrblClient", "NestedCRUDMixin", "_Schema"]
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class TigrblClient(RPCMixin, CRUDMixin, NestedCRUDMixin):
|
|
@@ -25,7 +25,7 @@ class TigrblClient(RPCMixin, CRUDMixin, NestedCRUDMixin):
|
|
|
25
25
|
* Async versions of all operations (aget, apost, aput, apatch, adelete)
|
|
26
26
|
* Connection pooling via httpx.Client
|
|
27
27
|
* Optional Pydantic schema validation for requests and responses
|
|
28
|
-
*
|
|
28
|
+
* Nested REST helpers for hierarchical resources
|
|
29
29
|
* Works with Tigrbl services that expose operations via resource-based
|
|
30
30
|
namespaces (e.g., ``api.core.Users.create`` or ``api.rpc.Users.login``)
|
|
31
31
|
|
|
@@ -72,7 +72,7 @@ class TigrblClient(RPCMixin, CRUDMixin, NestedCRUDMixin):
|
|
|
72
72
|
self._async_client = httpx.AsyncClient(timeout=10.0)
|
|
73
73
|
self._own_async = True # we always own the async client
|
|
74
74
|
|
|
75
|
-
#
|
|
75
|
+
# Enable nested resource helpers.
|
|
76
76
|
NestedCRUDMixin.__init__(self)
|
|
77
77
|
|
|
78
78
|
@property
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# tigrbl_client/_nested_crud.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from typing import Any, Iterable, TypeVar
|
|
5
|
+
|
|
6
|
+
T = TypeVar("T")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NestedCRUDMixin:
|
|
10
|
+
"""Helpers for hierarchical REST resource paths."""
|
|
11
|
+
|
|
12
|
+
def __init__(self) -> None:
|
|
13
|
+
self._nested_crud_enabled = True
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def _normalize_segment(value: Any) -> str:
|
|
17
|
+
raw = str(value).strip("/")
|
|
18
|
+
if not raw:
|
|
19
|
+
raise ValueError("Nested resource segments must be non-empty")
|
|
20
|
+
return raw
|
|
21
|
+
|
|
22
|
+
def nested_path(self, *segments: Any) -> str:
|
|
23
|
+
"""Build a normalized nested resource path.
|
|
24
|
+
|
|
25
|
+
Examples:
|
|
26
|
+
``nested_path("users", 1, "posts", 2) -> "/users/1/posts/2"``
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
if not segments:
|
|
30
|
+
raise ValueError("At least one nested segment is required")
|
|
31
|
+
normalized = [self._normalize_segment(segment) for segment in segments]
|
|
32
|
+
return "/" + "/".join(normalized)
|
|
33
|
+
|
|
34
|
+
def nested_collection_path(
|
|
35
|
+
self,
|
|
36
|
+
*parents: Any,
|
|
37
|
+
resource: str,
|
|
38
|
+
) -> str:
|
|
39
|
+
"""Build a nested collection path under one or more parent pairs."""
|
|
40
|
+
|
|
41
|
+
parent_segments = tuple(parents)
|
|
42
|
+
if len(parent_segments) % 2 != 0:
|
|
43
|
+
raise ValueError("parents must be resource/id pairs")
|
|
44
|
+
return self.nested_path(*parent_segments, resource)
|
|
45
|
+
|
|
46
|
+
def nested_member_path(
|
|
47
|
+
self,
|
|
48
|
+
*parents: Any,
|
|
49
|
+
resource: str,
|
|
50
|
+
item_id: Any,
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Build a nested member path under one or more parent pairs."""
|
|
53
|
+
|
|
54
|
+
return self.nested_path(*parents, resource, item_id)
|
|
55
|
+
|
|
56
|
+
def nested_get(self, *segments: Any, **kwargs: Any) -> Any:
|
|
57
|
+
return self.get(self.nested_path(*segments), **kwargs)
|
|
58
|
+
|
|
59
|
+
def nested_post(self, *segments: Any, **kwargs: Any) -> Any:
|
|
60
|
+
return self.post(self.nested_path(*segments), **kwargs)
|
|
61
|
+
|
|
62
|
+
def nested_put(self, *segments: Any, **kwargs: Any) -> Any:
|
|
63
|
+
return self.put(self.nested_path(*segments), **kwargs)
|
|
64
|
+
|
|
65
|
+
def nested_patch(self, *segments: Any, **kwargs: Any) -> Any:
|
|
66
|
+
return self.patch(self.nested_path(*segments), **kwargs)
|
|
67
|
+
|
|
68
|
+
def nested_delete(self, *segments: Any, **kwargs: Any) -> Any:
|
|
69
|
+
return self.delete(self.nested_path(*segments), **kwargs)
|
|
70
|
+
|
|
71
|
+
async def anested_get(self, *segments: Any, **kwargs: Any) -> Any:
|
|
72
|
+
return await self.aget(self.nested_path(*segments), **kwargs)
|
|
73
|
+
|
|
74
|
+
async def anested_post(self, *segments: Any, **kwargs: Any) -> Any:
|
|
75
|
+
return await self.apost(self.nested_path(*segments), **kwargs)
|
|
76
|
+
|
|
77
|
+
async def anested_put(self, *segments: Any, **kwargs: Any) -> Any:
|
|
78
|
+
return await self.aput(self.nested_path(*segments), **kwargs)
|
|
79
|
+
|
|
80
|
+
async def anested_patch(self, *segments: Any, **kwargs: Any) -> Any:
|
|
81
|
+
return await self.apatch(self.nested_path(*segments), **kwargs)
|
|
82
|
+
|
|
83
|
+
async def anested_delete(self, *segments: Any, **kwargs: Any) -> Any:
|
|
84
|
+
return await self.adelete(self.nested_path(*segments), **kwargs)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
__all__ = ["NestedCRUDMixin"]
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: tigrbl_client
|
|
3
|
-
Version: 0.2.2.dev27
|
|
4
|
-
Summary: A client for Tigrbl servers by Swarmauri.
|
|
5
|
-
License-Expression: Apache-2.0
|
|
6
|
-
License-File: LICENSE
|
|
7
|
-
Keywords: tigrbl,sdk,standards,client
|
|
8
|
-
Author: Jacob Stewart
|
|
9
|
-
Author-email: jacob@swarmauri.com
|
|
10
|
-
Requires-Python: >=3.10,<3.13
|
|
11
|
-
Classifier: License :: OSI Approved :: Apache Software License
|
|
12
|
-
Classifier: Development Status :: 3 - Alpha
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
-
Classifier: Programming Language :: Python
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Requires-Dist: fastapi (>=0.100.0)
|
|
20
|
-
Requires-Dist: pydantic (>=2.0.0)
|
|
21
|
-
Requires-Dist: swarmauri_base
|
|
22
|
-
Requires-Dist: swarmauri_core
|
|
23
|
-
Requires-Dist: swarmauri_standard
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-

|
|
27
|
-
|
|
28
|
-
<p align="center">
|
|
29
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
30
|
-
<img src="https://img.shields.io/pypi/v/tigrbl_client?label=tigrbl_client&color=green" alt="PyPI - tigrbl_client"/>
|
|
31
|
-
</a>
|
|
32
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
33
|
-
<img src="https://img.shields.io/pypi/dm/tigrbl_client" alt="PyPI - Downloads"/>
|
|
34
|
-
</a>
|
|
35
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
36
|
-
<img src="https://img.shields.io/pypi/pyversions/tigrbl_client" alt="PyPI - Python Version"/>
|
|
37
|
-
</a>
|
|
38
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
39
|
-
<img src="https://img.shields.io/pypi/l/tigrbl_client" alt="PyPI - License"/>
|
|
40
|
-
</a>
|
|
41
|
-
<a href="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/tigrbl_client/">
|
|
42
|
-
<img alt="Hits" src="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/tigrbl_client.svg"/>
|
|
43
|
-
</a>
|
|
44
|
-
</p>
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
# Tigrbl Client 🐅
|
|
49
|
-
|
|
50
|
-
A lightweight HTTP client for interacting with Tigrbl services.
|
|
51
|
-
Tigrbl exposes operations under resource-based namespaces such as
|
|
52
|
-
`api.core.Users.create` or `api.rpc.Users.login`, all accessible via
|
|
53
|
-
this client.
|
|
54
|
-
|
|
55
|
-
## Features ✨
|
|
56
|
-
|
|
57
|
-
- Minimal dependencies with friendly typing
|
|
58
|
-
- Automatic JSON serialization / deserialization
|
|
59
|
-
- Simple namespace-based method access
|
|
60
|
-
|
|
61
|
-
## Installation 📦
|
|
62
|
-
|
|
63
|
-
```bash
|
|
64
|
-
pip install tigrbl_client
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Quick Start 🚀
|
|
68
|
-
|
|
69
|
-
```python
|
|
70
|
-
from tigrbl_client import Client
|
|
71
|
-
|
|
72
|
-
client = Client("https://api.tigrbl.io", token="your-token")
|
|
73
|
-
response = client.api.core.Users.read(id=1)
|
|
74
|
-
print(response)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## License 📝
|
|
78
|
-
|
|
79
|
-
This project is licensed under the terms of the [MIT license](LICENSE).
|
|
80
|
-
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-

|
|
2
|
-
|
|
3
|
-
<p align="center">
|
|
4
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
5
|
-
<img src="https://img.shields.io/pypi/v/tigrbl_client?label=tigrbl_client&color=green" alt="PyPI - tigrbl_client"/>
|
|
6
|
-
</a>
|
|
7
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
8
|
-
<img src="https://img.shields.io/pypi/dm/tigrbl_client" alt="PyPI - Downloads"/>
|
|
9
|
-
</a>
|
|
10
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
11
|
-
<img src="https://img.shields.io/pypi/pyversions/tigrbl_client" alt="PyPI - Python Version"/>
|
|
12
|
-
</a>
|
|
13
|
-
<a href="https://pypi.org/project/tigrbl_client/">
|
|
14
|
-
<img src="https://img.shields.io/pypi/l/tigrbl_client" alt="PyPI - License"/>
|
|
15
|
-
</a>
|
|
16
|
-
<a href="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/tigrbl_client/">
|
|
17
|
-
<img alt="Hits" src="https://hits.sh/github.com/swarmauri/swarmauri-sdk/tree/master/pkgs/standards/tigrbl_client.svg"/>
|
|
18
|
-
</a>
|
|
19
|
-
</p>
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
# Tigrbl Client 🐅
|
|
24
|
-
|
|
25
|
-
A lightweight HTTP client for interacting with Tigrbl services.
|
|
26
|
-
Tigrbl exposes operations under resource-based namespaces such as
|
|
27
|
-
`api.core.Users.create` or `api.rpc.Users.login`, all accessible via
|
|
28
|
-
this client.
|
|
29
|
-
|
|
30
|
-
## Features ✨
|
|
31
|
-
|
|
32
|
-
- Minimal dependencies with friendly typing
|
|
33
|
-
- Automatic JSON serialization / deserialization
|
|
34
|
-
- Simple namespace-based method access
|
|
35
|
-
|
|
36
|
-
## Installation 📦
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
pip install tigrbl_client
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## Quick Start 🚀
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
from tigrbl_client import Client
|
|
46
|
-
|
|
47
|
-
client = Client("https://api.tigrbl.io", token="your-token")
|
|
48
|
-
response = client.api.core.Users.read(id=1)
|
|
49
|
-
print(response)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## License 📝
|
|
53
|
-
|
|
54
|
-
This project is licensed under the terms of the [MIT license](LICENSE).
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# tigrbl_client/_nested_crud.py
|
|
2
|
-
"""
|
|
3
|
-
Placeholder module for nested CRUD operations.
|
|
4
|
-
|
|
5
|
-
This module will be developed in the future to support nested resource paths
|
|
6
|
-
and more complex CRUD operations with hierarchical data structures.
|
|
7
|
-
|
|
8
|
-
Examples of future functionality:
|
|
9
|
-
- /users/{user_id}/posts/{post_id}/comments
|
|
10
|
-
- /organizations/{org_id}/teams/{team_id}/members
|
|
11
|
-
- Complex resource relationships and nested operations
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
from __future__ import annotations
|
|
15
|
-
|
|
16
|
-
from typing import Any, TypeVar, Protocol
|
|
17
|
-
from typing import runtime_checkable
|
|
18
|
-
|
|
19
|
-
T = TypeVar("T")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@runtime_checkable
|
|
23
|
-
class _Schema(Protocol[T]): # anything with Pydantic-v2 interface
|
|
24
|
-
@classmethod
|
|
25
|
-
def model_validate(cls, data: Any) -> T: ...
|
|
26
|
-
@classmethod
|
|
27
|
-
def model_dump_json(cls, **kw) -> str: ...
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
class NestedCRUDMixin:
|
|
31
|
-
"""
|
|
32
|
-
Placeholder mixin class for nested CRUD functionality.
|
|
33
|
-
|
|
34
|
-
This will be developed to support complex nested resource paths
|
|
35
|
-
and hierarchical data operations.
|
|
36
|
-
"""
|
|
37
|
-
|
|
38
|
-
def __init__(self):
|
|
39
|
-
"""Initialize the NestedCRUDMixin."""
|
|
40
|
-
# Placeholder for future initialization
|
|
41
|
-
pass
|
|
42
|
-
|
|
43
|
-
def _placeholder_method(self) -> str:
|
|
44
|
-
"""
|
|
45
|
-
Placeholder method to demonstrate future functionality.
|
|
46
|
-
|
|
47
|
-
Returns:
|
|
48
|
-
A message indicating this is a placeholder
|
|
49
|
-
"""
|
|
50
|
-
return "This is a placeholder for future nested CRUD functionality"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|