openadmin 0.2.0__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.
Files changed (37) hide show
  1. openadmin/fastapi/__init__.py +11 -0
  2. openadmin/fastapi/admin_page.py +526 -0
  3. openadmin/fastapi/admin_panel.py +89 -0
  4. openadmin/fastapi/types/__init__.py +36 -0
  5. openadmin/fastapi/types/action.py +20 -0
  6. openadmin/fastapi/types/area_chart.py +19 -0
  7. openadmin/fastapi/types/bar_chart.py +19 -0
  8. openadmin/fastapi/types/form.py +20 -0
  9. openadmin/fastapi/types/line_chart.py +19 -0
  10. openadmin/fastapi/types/markdown.py +19 -0
  11. openadmin/fastapi/types/page_protocol.py +12 -0
  12. openadmin/fastapi/types/pie_chart.py +19 -0
  13. openadmin/fastapi/types/section.py +15 -0
  14. openadmin/fastapi/types/stat.py +19 -0
  15. openadmin/fastapi/types/table.py +19 -0
  16. openadmin/fastapi/utils.py +167 -0
  17. openadmin/spec/__init__.py +41 -0
  18. openadmin/spec/components/__init__.py +35 -0
  19. openadmin/spec/components/action.py +22 -0
  20. openadmin/spec/components/area_chart.py +19 -0
  21. openadmin/spec/components/bar_chart.py +19 -0
  22. openadmin/spec/components/form.py +22 -0
  23. openadmin/spec/components/http_methods.py +7 -0
  24. openadmin/spec/components/line_chart.py +19 -0
  25. openadmin/spec/components/markdown.py +19 -0
  26. openadmin/spec/components/pie_chart.py +19 -0
  27. openadmin/spec/components/property.py +17 -0
  28. openadmin/spec/components/property_type.py +18 -0
  29. openadmin/spec/components/stat.py +19 -0
  30. openadmin/spec/components/table.py +21 -0
  31. openadmin/spec/page.py +15 -0
  32. openadmin/spec/section.py +15 -0
  33. openadmin/spec/spec.py +16 -0
  34. openadmin-0.2.0.dist-info/METADATA +304 -0
  35. openadmin-0.2.0.dist-info/RECORD +37 -0
  36. openadmin-0.2.0.dist-info/WHEEL +4 -0
  37. openadmin-0.2.0.dist-info/licenses/LICENSE +661 -0
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class BarChart(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ name: str
17
+ description: str | None
18
+ method: spec.HttpMethod
19
+ func: Callable | None = None
@@ -0,0 +1,20 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class Form(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ name: str
17
+ description: str | None
18
+ method: spec.HttpMethod
19
+ is_hiden: bool
20
+ func: Callable | None = None
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class LineChart(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ name: str
17
+ description: str | None
18
+ method: spec.HttpMethod
19
+ func: Callable | None = None
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class Markdown(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ name: str
17
+ description: str | None
18
+ method: spec.HttpMethod
19
+ func: Callable | None = None
@@ -0,0 +1,12 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Protocol
6
+
7
+ from fastapi import FastAPI
8
+ from openadmin import spec
9
+
10
+
11
+ class PageProtocol(Protocol):
12
+ def get_page_spec(self, app: FastAPI) -> spec.Page: ...
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class PieChart(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ name: str
17
+ description: str | None
18
+ method: spec.HttpMethod
19
+ func: Callable | None = None
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Sequence
7
+
8
+ from .page_protocol import PageProtocol
9
+
10
+
11
+ @dataclass
12
+ class Section:
13
+ name: str
14
+ description: str | None
15
+ pages: Sequence[PageProtocol]
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class Stat(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ method: spec.HttpMethod
17
+ name: str
18
+ description: str | None
19
+ func: Callable | None = None
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from collections.abc import Callable
6
+
7
+ from pydantic import BaseModel, ConfigDict
8
+
9
+ from openadmin import spec
10
+
11
+
12
+ class Table(BaseModel):
13
+ model_config = ConfigDict(arbitrary_types_allowed=True)
14
+
15
+ function_name: str
16
+ method: spec.HttpMethod
17
+ name: str
18
+ description: str | None
19
+ func: Callable | None = None
@@ -0,0 +1,167 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ import inspect
6
+ from typing import Annotated, get_args, get_origin, get_type_hints
7
+
8
+ import pydantic
9
+
10
+ from fastapi.params import Body as BodyParam
11
+ from fastapi.params import Form as FormParam
12
+ from fastapi.params import Query as QueryParam
13
+ from openadmin import spec
14
+
15
+ _SCALAR_LIST_TYPES: dict[type, spec.PropertyType] = {
16
+ str: "list[string]",
17
+ int: "list[integer]",
18
+ float: "list[float]",
19
+ bool: "list[bool]",
20
+ }
21
+
22
+
23
+ def _type_to_property_type(tp) -> spec.PropertyType:
24
+ origin = get_origin(tp)
25
+ if origin is list:
26
+ inner = get_args(tp)
27
+ if inner:
28
+ scalar = _SCALAR_LIST_TYPES.get(inner[0])
29
+ if scalar:
30
+ return scalar
31
+ return "list"
32
+ if tp is str:
33
+ return "string"
34
+ if tp is int:
35
+ return "integer"
36
+ if tp is float:
37
+ return "float"
38
+ if tp is bool:
39
+ return "bool"
40
+ if isinstance(tp, type) and issubclass(tp, pydantic.BaseModel):
41
+ return "object"
42
+ return "string"
43
+
44
+
45
+ def _model_to_properties(model: type) -> list[spec.Property]:
46
+ props = []
47
+ for field_name, field_info in model.model_fields.items():
48
+ tp = field_info.annotation
49
+ prop_type = _type_to_property_type(tp)
50
+ nested = None
51
+ if (
52
+ prop_type == "object"
53
+ and isinstance(tp, type)
54
+ and issubclass(tp, pydantic.BaseModel)
55
+ ):
56
+ nested = _model_to_properties(tp)
57
+ elif prop_type == "list":
58
+ inner = get_args(tp)
59
+ if (
60
+ inner
61
+ and isinstance(inner[0], type)
62
+ and issubclass(inner[0], pydantic.BaseModel)
63
+ ):
64
+ nested = _model_to_properties(inner[0])
65
+ display = field_info.title or field_name.replace("_", " ").title()
66
+ alias = field_info.alias or field_name
67
+ props.append(
68
+ spec.Property(
69
+ name=display,
70
+ alias=alias,
71
+ type=prop_type,
72
+ is_required=field_info.is_required(),
73
+ properties=nested,
74
+ )
75
+ )
76
+ return props
77
+
78
+
79
+ def _make_property(param_name: str, tp, marker) -> spec.Property:
80
+ prop_type = _type_to_property_type(tp)
81
+ nested = None
82
+ if (
83
+ prop_type == "object"
84
+ and isinstance(tp, type)
85
+ and issubclass(tp, pydantic.BaseModel)
86
+ ):
87
+ nested = _model_to_properties(tp)
88
+
89
+ display = param_name.replace("_", " ").title()
90
+ alias = param_name
91
+ required = True
92
+
93
+ if marker is not None:
94
+ if marker.alias:
95
+ alias = marker.alias
96
+ if marker.title:
97
+ display = marker.title
98
+ required = marker.is_required()
99
+
100
+ return spec.Property(
101
+ name=display,
102
+ alias=alias,
103
+ type=prop_type,
104
+ is_required=required,
105
+ properties=nested,
106
+ )
107
+
108
+
109
+ def extract_params(
110
+ func,
111
+ ) -> tuple[
112
+ list[spec.Property] | None, list[spec.Property] | None, list[spec.Property] | None
113
+ ]:
114
+ """Return (query, body, form) properties extracted from a function's signature."""
115
+ try:
116
+ hints = get_type_hints(func, include_extras=True)
117
+ except Exception:
118
+ return None, None, None
119
+
120
+ sig = inspect.signature(func)
121
+ query: list[spec.Property] = []
122
+ body: list[spec.Property] = []
123
+ form: list[spec.Property] = []
124
+
125
+ for param_name, param in sig.parameters.items():
126
+ if param_name in ("self", "cls"):
127
+ continue
128
+
129
+ annotation = hints.get(param_name, inspect.Parameter.empty)
130
+ default = param.default
131
+ marker = None
132
+ actual_type = annotation
133
+
134
+ if get_origin(annotation) is Annotated:
135
+ args = get_args(annotation)
136
+ actual_type = args[0]
137
+ for arg in args[1:]:
138
+ if isinstance(arg, (QueryParam, BodyParam, FormParam)):
139
+ marker = arg
140
+ break
141
+
142
+ if marker is None and isinstance(default, (QueryParam, BodyParam, FormParam)):
143
+ marker = default
144
+
145
+ # FormParam must be checked before BodyParam — Form is a subclass of Body
146
+ if isinstance(marker, QueryParam):
147
+ query.append(_make_property(param_name, actual_type, marker))
148
+ elif isinstance(marker, FormParam):
149
+ form.append(_make_property(param_name, actual_type, marker))
150
+ elif isinstance(marker, BodyParam):
151
+ if isinstance(actual_type, type) and issubclass(
152
+ actual_type, pydantic.BaseModel
153
+ ):
154
+ body.extend(_model_to_properties(actual_type))
155
+ else:
156
+ body.append(_make_property(param_name, actual_type, marker))
157
+ elif (
158
+ annotation is not inspect.Parameter.empty
159
+ and default is inspect.Parameter.empty
160
+ ):
161
+ # Unannotated Pydantic model with no default → implicit JSON body
162
+ if isinstance(actual_type, type) and issubclass(
163
+ actual_type, pydantic.BaseModel
164
+ ):
165
+ body.extend(_model_to_properties(actual_type))
166
+
167
+ return query or None, body or None, form or None
@@ -0,0 +1,41 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from .components import (
6
+ Action,
7
+ AreaChart,
8
+ BarChart,
9
+ Component,
10
+ Form,
11
+ LineChart,
12
+ Markdown,
13
+ PieChart,
14
+ Property,
15
+ PropertyType,
16
+ Stat,
17
+ Table,
18
+ )
19
+ from .components.http_methods import HttpMethod
20
+ from .page import Page
21
+ from .section import Section
22
+ from .spec import Spec
23
+
24
+ __all__ = [
25
+ "Action",
26
+ "AreaChart",
27
+ "BarChart",
28
+ "Component",
29
+ "Form",
30
+ "HttpMethod",
31
+ "LineChart",
32
+ "Page",
33
+ "PieChart",
34
+ "Property",
35
+ "PropertyType",
36
+ "Section",
37
+ "Spec",
38
+ "Stat",
39
+ "Table",
40
+ "Markdown",
41
+ ]
@@ -0,0 +1,35 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Union
6
+
7
+ from .action import Action
8
+ from .area_chart import AreaChart
9
+ from .bar_chart import BarChart
10
+ from .form import Form
11
+ from .line_chart import LineChart
12
+ from .markdown import Markdown
13
+ from .pie_chart import PieChart
14
+ from .property import Property
15
+ from .property_type import PropertyType
16
+ from .stat import Stat
17
+ from .table import Table
18
+
19
+ type Component = Union[
20
+ Stat, Table, AreaChart, BarChart, LineChart, PieChart, Action, Form, Markdown
21
+ ]
22
+
23
+ __all__ = [
24
+ "AreaChart",
25
+ "BarChart",
26
+ "Component",
27
+ "LineChart",
28
+ "PieChart",
29
+ "Stat",
30
+ "Action",
31
+ "Table",
32
+ "Form",
33
+ "Property",
34
+ "PropertyType",
35
+ ]
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class Action(BaseModel):
14
+ type: Literal["action"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ is_hidden: bool
20
+ form: List[Property] | None = Field(None)
21
+ body: List[Property] | None = Field(None)
22
+ query: List[Property] | None = Field(None)
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class AreaChart(BaseModel):
14
+ type: Literal["area-chart"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ query: list[Property] | None = Field(None)
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class BarChart(BaseModel):
14
+ type: Literal["bar-chart"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ query: list[Property] | None = Field(None)
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class Form(BaseModel):
14
+ type: Literal["form"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ is_hiden: bool
20
+ form: List[Property] | None = Field(None)
21
+ body: List[Property] | None = Field(None)
22
+ query: List[Property] | None = Field(None)
@@ -0,0 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ type HttpMethod = Literal["get", "post", "put", "patch", "delete", "head"]
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class LineChart(BaseModel):
14
+ type: Literal["line-chart"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ query: list[Property] | None = Field(None)
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class Markdown(BaseModel):
14
+ type: Literal["markdown"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ query: List[Property] | None = Field(None)
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class PieChart(BaseModel):
14
+ type: Literal["pie-chart"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ query: list[Property] | None = Field(None)
@@ -0,0 +1,17 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .property_type import PropertyType
10
+
11
+
12
+ class Property(BaseModel):
13
+ name: str = Field(..., description="This name for showing to user in admin panel")
14
+ alias: str = Field(..., description="This name goes to body or form for backend")
15
+ type: PropertyType
16
+ is_required: bool
17
+ properties: List[Property] | None = Field(None)
@@ -0,0 +1,18 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ type PropertyType = Literal[
8
+ "integer",
9
+ "string",
10
+ "bool",
11
+ "float",
12
+ "object",
13
+ "list",
14
+ "list[integer]",
15
+ "list[string]",
16
+ "list[bool]",
17
+ "list[float]",
18
+ ]
@@ -0,0 +1,19 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class Stat(BaseModel):
14
+ type: Literal["stat"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ query: list[Property] | None = Field(None)
@@ -0,0 +1,21 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List, Literal
6
+
7
+ from pydantic import BaseModel, Field
8
+
9
+ from .http_methods import HttpMethod
10
+ from .property import Property
11
+
12
+
13
+ class Table(BaseModel):
14
+ type: Literal["table"]
15
+ name: str
16
+ description: str | None
17
+ url: str
18
+ method: HttpMethod
19
+ form: List[Property] | None = Field(None)
20
+ body: List[Property] | None = Field(None)
21
+ query: List[Property] | None = Field(None)
openadmin/spec/page.py ADDED
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from .components import Component
10
+
11
+
12
+ class Page(BaseModel):
13
+ name: str
14
+ description: str | None
15
+ components: List[Component]
@@ -0,0 +1,15 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from typing import List
6
+
7
+ from pydantic import BaseModel
8
+
9
+ from .page import Page
10
+
11
+
12
+ class Section(BaseModel):
13
+ name: str
14
+ description: str | None
15
+ pages: List[Page]