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.
- openadmin/fastapi/__init__.py +11 -0
- openadmin/fastapi/admin_page.py +526 -0
- openadmin/fastapi/admin_panel.py +89 -0
- openadmin/fastapi/types/__init__.py +36 -0
- openadmin/fastapi/types/action.py +20 -0
- openadmin/fastapi/types/area_chart.py +19 -0
- openadmin/fastapi/types/bar_chart.py +19 -0
- openadmin/fastapi/types/form.py +20 -0
- openadmin/fastapi/types/line_chart.py +19 -0
- openadmin/fastapi/types/markdown.py +19 -0
- openadmin/fastapi/types/page_protocol.py +12 -0
- openadmin/fastapi/types/pie_chart.py +19 -0
- openadmin/fastapi/types/section.py +15 -0
- openadmin/fastapi/types/stat.py +19 -0
- openadmin/fastapi/types/table.py +19 -0
- openadmin/fastapi/utils.py +167 -0
- openadmin/spec/__init__.py +41 -0
- openadmin/spec/components/__init__.py +35 -0
- openadmin/spec/components/action.py +22 -0
- openadmin/spec/components/area_chart.py +19 -0
- openadmin/spec/components/bar_chart.py +19 -0
- openadmin/spec/components/form.py +22 -0
- openadmin/spec/components/http_methods.py +7 -0
- openadmin/spec/components/line_chart.py +19 -0
- openadmin/spec/components/markdown.py +19 -0
- openadmin/spec/components/pie_chart.py +19 -0
- openadmin/spec/components/property.py +17 -0
- openadmin/spec/components/property_type.py +18 -0
- openadmin/spec/components/stat.py +19 -0
- openadmin/spec/components/table.py +21 -0
- openadmin/spec/page.py +15 -0
- openadmin/spec/section.py +15 -0
- openadmin/spec/spec.py +16 -0
- openadmin-0.2.0.dist-info/METADATA +304 -0
- openadmin-0.2.0.dist-info/RECORD +37 -0
- openadmin-0.2.0.dist-info/WHEEL +4 -0
- openadmin-0.2.0.dist-info/licenses/LICENSE +661 -0
openadmin/spec/spec.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
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 .section import Section
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Spec(BaseModel):
|
|
13
|
+
version: str
|
|
14
|
+
name: str
|
|
15
|
+
description: str | None = None
|
|
16
|
+
sections: List[Section]
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openadmin
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Project-URL: Homepage, https://github.com/openadmin-team/openadmin-py
|
|
6
|
+
Project-URL: Issues, https://github.com/openadmin-team/openadmin-py/issues
|
|
7
|
+
Author-email: Mykyta Kasianenko <mykytakasianenko@gmail.com>
|
|
8
|
+
License-Expression: AGPL-3.0-or-later
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.14
|
|
13
|
+
Requires-Dist: fastapi[standard]>=0.136.3
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<!-- <div align="center">
|
|
17
|
+
<img src="docs/assets/logo.png" alt="OpenAdmin" width="180" />
|
|
18
|
+
|
|
19
|
+
<h1>openadmin-py</h1>
|
|
20
|
+
|
|
21
|
+
<p>Build admin panels as FastAPI routes — stats, tables, charts, and actions, all in pure Python.</p>
|
|
22
|
+
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
[](https://python.org)
|
|
25
|
+
[](https://fastapi.tiangolo.com)
|
|
26
|
+
</div> -->
|
|
27
|
+
# 🚧 Project is under development 🚧
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
**OpenAdmin** is a FastAPI-native library for building admin dashboards. Define pages with typed decorators — no frontend code, no templates, no configuration files. Every page is just a router; every widget is just an endpoint
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- **Stats** — display single values: counts, totals, booleans, percentages
|
|
35
|
+
- **Tables** — paginated, searchable data grids with per-row actions
|
|
36
|
+
- **Charts** — area, bar, line, and pie charts with labeled series
|
|
37
|
+
- **Actions** — trigger HTTP calls (GET / POST / PUT / PATCH / DELETE) from the UI
|
|
38
|
+
- **Forms** — structured forms that POST/PUT/PATCH/DELETE to your own endpoints
|
|
39
|
+
- **Markdown** — render rich text content on any page
|
|
40
|
+
- **FastAPI-native** — full dependency injection, OpenAPI docs, and router composition out of the box
|
|
41
|
+
|
|
42
|
+
## Installation
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install openadmin-py
|
|
46
|
+
# or with uv
|
|
47
|
+
uv add openadmin-py
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Quick Start
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
from fastapi import FastAPI
|
|
54
|
+
from openadmin.fastapi import AdminPanel, AdminPage
|
|
55
|
+
from openadmin.types import Stat, Table
|
|
56
|
+
|
|
57
|
+
# Create an admin panel (a FastAPI sub-application)
|
|
58
|
+
admin = AdminPanel()
|
|
59
|
+
|
|
60
|
+
# Define a page
|
|
61
|
+
page = AdminPage("Dashboard")
|
|
62
|
+
|
|
63
|
+
@page.stat("Total Users")
|
|
64
|
+
async def total_users() -> Stat:
|
|
65
|
+
return Stat(value=1_024)
|
|
66
|
+
|
|
67
|
+
@page.table("Recent Users")
|
|
68
|
+
async def recent_users() -> Table:
|
|
69
|
+
return Table(data=[
|
|
70
|
+
{"id": 1, "name": "Alice", "role": "admin"},
|
|
71
|
+
{"id": 2, "name": "Bob", "role": "viewer"},
|
|
72
|
+
])
|
|
73
|
+
|
|
74
|
+
# Register the page and mount the panel
|
|
75
|
+
admin.include_page(page)
|
|
76
|
+
|
|
77
|
+
app = FastAPI()
|
|
78
|
+
app.mount("/admin", admin)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
### Stats
|
|
84
|
+
|
|
85
|
+
Display a single numeric or boolean value.
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from openadmin.types import Stat
|
|
89
|
+
|
|
90
|
+
@page.stat("Active Sessions")
|
|
91
|
+
async def active_sessions() -> Stat:
|
|
92
|
+
return Stat(value=42)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Tables
|
|
96
|
+
|
|
97
|
+
Paginated tables with optional search. Use the built-in dependency types to receive pagination and search parameters automatically.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from openadmin.fastapi import PaginationParamsDep, SearchQueryDep
|
|
101
|
+
from openadmin.types import Table
|
|
102
|
+
|
|
103
|
+
@page.table("Users")
|
|
104
|
+
async def users_table(
|
|
105
|
+
pagination: PaginationParamsDep,
|
|
106
|
+
search: SearchQueryDep,
|
|
107
|
+
) -> Table:
|
|
108
|
+
# pagination.page, pagination.per_page
|
|
109
|
+
# search is str | None
|
|
110
|
+
return Table(data=[...])
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### Row Actions
|
|
114
|
+
|
|
115
|
+
Attach per-row action buttons by including `__actions__` in each row:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from openadmin.types import Action, Table, TableRow
|
|
119
|
+
|
|
120
|
+
@page.table("Users")
|
|
121
|
+
async def users_table() -> Table:
|
|
122
|
+
return Table(data=[
|
|
123
|
+
TableRow(
|
|
124
|
+
id=1,
|
|
125
|
+
name="Alice",
|
|
126
|
+
__actions__=[
|
|
127
|
+
Action(color="danger", method="DELETE", url="/users/1", body=None),
|
|
128
|
+
Action(color="info", method="POST", url="/users/1/reset", body=None),
|
|
129
|
+
],
|
|
130
|
+
)
|
|
131
|
+
])
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Charts
|
|
135
|
+
|
|
136
|
+
All chart types share the same structure: a `data` list of dicts and a `config` that maps series keys to display labels and colors.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from openadmin.types import BarChart, PieChart
|
|
140
|
+
|
|
141
|
+
@page.bar_chart("Sales by Region", "Total sales per region this quarter")
|
|
142
|
+
async def sales_chart() -> BarChart:
|
|
143
|
+
return BarChart(
|
|
144
|
+
data=[
|
|
145
|
+
{"region": "North", "sales": 120},
|
|
146
|
+
{"region": "South", "sales": 95},
|
|
147
|
+
],
|
|
148
|
+
config={"sales": {"label": "Sales", "color": "#6366f1"}},
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@page.pie_chart("User Roles", "Breakdown of user roles")
|
|
152
|
+
async def roles_chart() -> PieChart:
|
|
153
|
+
return PieChart(
|
|
154
|
+
data=[
|
|
155
|
+
{"segment": "Admin", "count": 5},
|
|
156
|
+
{"segment": "Editor", "count": 20},
|
|
157
|
+
{"segment": "Viewer", "count": 75},
|
|
158
|
+
],
|
|
159
|
+
config={"count": {"label": "Users", "color": "#10b981"}},
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Actions
|
|
164
|
+
|
|
165
|
+
Expose buttons that trigger HTTP requests — useful for one-off operations like cache clearing or triggering background jobs.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
@page.action_post("Clear Cache")
|
|
169
|
+
async def clear_cache():
|
|
170
|
+
# your logic here
|
|
171
|
+
return {"status": "cleared"}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Forms
|
|
175
|
+
|
|
176
|
+
Forms collect user input and submit it to your endpoint. Mark a form hidden if it should not appear in the page navigation.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from pydantic import BaseModel
|
|
180
|
+
|
|
181
|
+
class InvitePayload(BaseModel):
|
|
182
|
+
email: str
|
|
183
|
+
role: str
|
|
184
|
+
|
|
185
|
+
@page.form_post("Invite User", "Send an invitation email to a new user")
|
|
186
|
+
async def invite_user(payload: InvitePayload):
|
|
187
|
+
# send invite...
|
|
188
|
+
return {"invited": payload.email}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Markdown
|
|
192
|
+
|
|
193
|
+
Render markdown text directly on a page — useful for instructions, changelogs, or documentation sections.
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
@page.markdown("Release Notes")
|
|
197
|
+
async def release_notes() -> str:
|
|
198
|
+
return "## v1.2.0\n- Added dark mode\n- Fixed pagination bug"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Full Example
|
|
202
|
+
|
|
203
|
+
A real-world page querying a SQLAlchemy database:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from sqlalchemy import func, select
|
|
207
|
+
from openadmin.fastapi import AdminPage, PaginationParamsDep, SearchQueryDep
|
|
208
|
+
from openadmin.types import Stat, Table, BarChart
|
|
209
|
+
|
|
210
|
+
page = AdminPage("Library")
|
|
211
|
+
|
|
212
|
+
@page.stat("Total Books")
|
|
213
|
+
async def total_books(session: AsyncSessionDep) -> Stat:
|
|
214
|
+
result = await session.execute(select(func.count()).select_from(Book))
|
|
215
|
+
return Stat(value=result.scalar_one())
|
|
216
|
+
|
|
217
|
+
@page.table("Books")
|
|
218
|
+
async def books_table(
|
|
219
|
+
session: AsyncSessionDep,
|
|
220
|
+
pagination: PaginationParamsDep,
|
|
221
|
+
search: SearchQueryDep,
|
|
222
|
+
) -> Table:
|
|
223
|
+
stmt = select(Book).offset(pagination.page * pagination.per_page).limit(pagination.per_page)
|
|
224
|
+
books = (await session.execute(stmt)).scalars().all()
|
|
225
|
+
return Table(data=[{"title": b.title, "year": b.published_year} for b in books])
|
|
226
|
+
|
|
227
|
+
@page.bar_chart("Books per Genre", "Number of books in each genre")
|
|
228
|
+
async def books_per_genre(session: AsyncSessionDep) -> BarChart:
|
|
229
|
+
rows = (await session.execute(
|
|
230
|
+
select(Genre.name, func.count().label("books"))
|
|
231
|
+
.join(BookToGenre).group_by(Genre.id)
|
|
232
|
+
)).all()
|
|
233
|
+
return BarChart(
|
|
234
|
+
data=[{"genre": name, "books": count} for name, count in rows],
|
|
235
|
+
config={"books": {"label": "Books", "color": "#6366f1"}},
|
|
236
|
+
)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
See the [`examples/`](examples/) directory for a complete runnable application.
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
make dev/run
|
|
243
|
+
# → http://127.0.0.1:8000/docs
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## API Reference
|
|
247
|
+
|
|
248
|
+
### `AdminPanel`
|
|
249
|
+
|
|
250
|
+
A `FastAPI` subclass. Use `include_page(page)` to register an `AdminPage`.
|
|
251
|
+
|
|
252
|
+
| Method | Description |
|
|
253
|
+
|---|---|
|
|
254
|
+
| `include_page(page, tags=None)` | Mount an `AdminPage` onto the panel |
|
|
255
|
+
|
|
256
|
+
### `AdminPage`
|
|
257
|
+
|
|
258
|
+
An `APIRouter` subclass. Each decorator creates a typed GET (or POST/PUT/etc.) endpoint under the page's prefix.
|
|
259
|
+
|
|
260
|
+
| Decorator | HTTP | Response type |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| `@page.stat(name)` | GET | `Stat` |
|
|
263
|
+
| `@page.table(name)` | GET | `Table` |
|
|
264
|
+
| `@page.markdown(name)` | GET | `str` |
|
|
265
|
+
| `@page.area_chart(name, description)` | GET | `AreaChart` |
|
|
266
|
+
| `@page.bar_chart(name, description)` | GET | `BarChart` |
|
|
267
|
+
| `@page.line_chart(name, description)` | GET | `LineChart` |
|
|
268
|
+
| `@page.pie_chart(name, description)` | GET | `PieChart` |
|
|
269
|
+
| `@page.action_get/post/put/patch/delete(name)` | * | any |
|
|
270
|
+
| `@page.form_post/put/patch/delete(name, description)` | * | any |
|
|
271
|
+
|
|
272
|
+
### Dependencies
|
|
273
|
+
|
|
274
|
+
| Name | Type | Description |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| `PaginationParamsDep` | `PaginationParams` | `page` and `per_page` query params |
|
|
277
|
+
| `SearchQueryDep` | `str \| None` | `search` query param |
|
|
278
|
+
|
|
279
|
+
### Types
|
|
280
|
+
|
|
281
|
+
| Type | Fields |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `Stat` | `value: str \| bool \| int \| float` |
|
|
284
|
+
| `Table` | `data: list[TableRow \| dict]` |
|
|
285
|
+
| `TableRow` | any fields + `__actions__: list[Action]` |
|
|
286
|
+
| `Action` | `color`, `method`, `url`, `body` |
|
|
287
|
+
| `AreaChart / BarChart / LineChart / PieChart` | `data: list[dict]`, `config: dict` |
|
|
288
|
+
|
|
289
|
+
## Development
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# Run the example app
|
|
293
|
+
make dev/run
|
|
294
|
+
|
|
295
|
+
# Run all checks (format, lint, types, tests, security)
|
|
296
|
+
make check
|
|
297
|
+
|
|
298
|
+
# Auto-fix formatting and lint issues
|
|
299
|
+
make fix
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## License
|
|
303
|
+
|
|
304
|
+
[AGPL-3.0-or-later](LICENSE) — © 2026 OpenAdmin
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
openadmin/fastapi/__init__.py,sha256=RiqYntBCCLt9snCMc3D-h3Y-1mTrqc9lHNmkFIjw9FI,209
|
|
2
|
+
openadmin/fastapi/admin_page.py,sha256=kwEBjxB9iso6mtJmm41goEY-a8RTuF6fcDMzEq0Fk1E,15478
|
|
3
|
+
openadmin/fastapi/admin_panel.py,sha256=LzpEjIL17Xxf1rXq4e6W2pzG9SErdlFB8NjQxEj7lys,2609
|
|
4
|
+
openadmin/fastapi/utils.py,sha256=eleWv0kWZgndSJI2vFTR3C4vlNabL0Nxz1QnuxP6XhA,5180
|
|
5
|
+
openadmin/fastapi/types/__init__.py,sha256=VQRRr668R0npyVWYvDSKXoAx4JHbutynHUqOdgEX34E,754
|
|
6
|
+
openadmin/fastapi/types/action.py,sha256=MzFtamoavThIXrFkujBJMntZkJGv4JT5IrqOt_Blaxg,432
|
|
7
|
+
openadmin/fastapi/types/area_chart.py,sha256=aoLAR6oWSI3vw14zGxRKlnlrnxJLqDDYI8BCk8oOAM8,415
|
|
8
|
+
openadmin/fastapi/types/bar_chart.py,sha256=Gl9n93moh7sfFPf4CvZLmjLEuWjy5AdQaOWFi06ACPU,414
|
|
9
|
+
openadmin/fastapi/types/form.py,sha256=0Ww-7vrFJP9c5J1_oGbug3fDzrWXOp7M3pYC9LRuhAo,429
|
|
10
|
+
openadmin/fastapi/types/line_chart.py,sha256=OWYhEXHPFob-SPnu7shz6Zqf5x1NlqEUt__YW1Gc4jc,415
|
|
11
|
+
openadmin/fastapi/types/markdown.py,sha256=2g8uX_n2Shx-6r8ukehv9LMA4iD6i7ynuph65gRgFaQ,414
|
|
12
|
+
openadmin/fastapi/types/page_protocol.py,sha256=zMio8SamTFO6_Dxp_8_xnErTC6VEfryVtW_5jOM6oDw,265
|
|
13
|
+
openadmin/fastapi/types/pie_chart.py,sha256=MYp1mg7YlEgAA4RiPaz0g-0ynlSRflPc4nZz85soG2M,414
|
|
14
|
+
openadmin/fastapi/types/section.py,sha256=oWo5caUkLrdmvXnc3pdCC3cwLk8iMT4slrH5GnxI0E8,296
|
|
15
|
+
openadmin/fastapi/types/stat.py,sha256=fWit9vyIEkVzkf3IxkV73lAtttxgPfWOMfsVQDP44q8,410
|
|
16
|
+
openadmin/fastapi/types/table.py,sha256=JzYkV8zTa5rMmMh-tnmVm8Vl_LJefFXPsT0tJvTMjJo,411
|
|
17
|
+
openadmin/spec/__init__.py,sha256=9ZZnQz67JN7TyZhoI43G7aNKkOidXFX5Lxjkr41lYQ8,660
|
|
18
|
+
openadmin/spec/page.py,sha256=XCiQM_37lTQjjMQjWhrWSHof-pD_j69HZu5n27iHfcA,279
|
|
19
|
+
openadmin/spec/section.py,sha256=9qZny4rGqHeMpl2VApF8vqLuR-oMsc2kE1DmQdO62BI,261
|
|
20
|
+
openadmin/spec/spec.py,sha256=9wMyVDj4yMUkEdI-5f3FAYLDqXJ_FddME9padayB5Fs,294
|
|
21
|
+
openadmin/spec/components/__init__.py,sha256=jmm-FRcjJ_aDq_TU4mX4YJSUe1FTo9gmNPuF6GrRIfo,741
|
|
22
|
+
openadmin/spec/components/action.py,sha256=a4YAHS8kHKKHItP4_he2_rZq3z3gYf6U0ZmGAgPcJRE,522
|
|
23
|
+
openadmin/spec/components/area_chart.py,sha256=PlQmnTBSlsGESrW_LcBsxRnbfjfWroTbKEgNcVsVoR4,411
|
|
24
|
+
openadmin/spec/components/bar_chart.py,sha256=HccaLgfUsXZ5LASBBhVYp9atVbT6-Ek5fsM_gyRwPLg,409
|
|
25
|
+
openadmin/spec/components/form.py,sha256=683FleQsJPGQbJzWBn2fS3W94SGSxmd_6MpYTxGPTC4,517
|
|
26
|
+
openadmin/spec/components/http_methods.py,sha256=gRcLLnMCHvXYyq_8T4aVmjxy_kfOAc7x8SLc3MJmAl0,192
|
|
27
|
+
openadmin/spec/components/line_chart.py,sha256=Ebqk1Q-lgY2839iAumV5_9ZTdw5TKIOSP3kvg9xUKCs,411
|
|
28
|
+
openadmin/spec/components/markdown.py,sha256=u3zH8CGxR4udXFWNY7iqy1Cek44kGY28mO78ta3j8AU,414
|
|
29
|
+
openadmin/spec/components/pie_chart.py,sha256=lQC2Ot7UT4typoa3D11NCQ4HzCXrWQubWtXGamXi6kI,409
|
|
30
|
+
openadmin/spec/components/property.py,sha256=svoPxt3miGmK_ySFPFJmGdfZIAYBxaK37VsYwUjctbI,492
|
|
31
|
+
openadmin/spec/components/property_type.py,sha256=m1eJ7H5YIP4dhaa899Qc9BCRkZOwP_DELl6vPZB31HY,306
|
|
32
|
+
openadmin/spec/components/stat.py,sha256=sXQttzMJulS68foVeJ2Y0W4-UH_FSa4nOnxiLfQSGGE,400
|
|
33
|
+
openadmin/spec/components/table.py,sha256=8dJFnKNj-yOE85mFWQVdoku5tTWA4UZHwV8zId0z6j8,500
|
|
34
|
+
openadmin-0.2.0.dist-info/METADATA,sha256=-OxWijqAtLNrdTJ-qVt8wwuWEMgT5trug82D4aTgTlQ,8936
|
|
35
|
+
openadmin-0.2.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
36
|
+
openadmin-0.2.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
37
|
+
openadmin-0.2.0.dist-info/RECORD,,
|