half-orm-gen 1.0.0a1__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.
- half_orm_gen/__init__.py +16 -0
- half_orm_gen/api_routes.py +224 -0
- half_orm_gen/cli_extension.py +170 -0
- half_orm_gen/crud_routes.py +505 -0
- half_orm_gen/gen_app/__init__.py +26 -0
- half_orm_gen/gen_app/angular.py +1727 -0
- half_orm_gen/gen_app/svelte.py +1336 -0
- half_orm_gen/gen_store/__init__.py +34 -0
- half_orm_gen/gen_store/base.py +88 -0
- half_orm_gen/gen_store/svelte.py +282 -0
- half_orm_gen/generate.py +120 -0
- half_orm_gen/scaffold.py +37 -0
- half_orm_gen/scaffolding/api_init.py +1 -0
- half_orm_gen/scaffolding/custom_authorization.py +36 -0
- half_orm_gen/scaffolding/custom_init.py +1 -0
- half_orm_gen/scaffolding/custom_middlewares_init.py +18 -0
- half_orm_gen/scaffolding/custom_routes.py +18 -0
- half_orm_gen/scaffolding/guards.py +40 -0
- half_orm_gen/scaffolding/roles_core.py +62 -0
- half_orm_gen/templates.py +454 -0
- half_orm_gen/templates_fastapi.py +264 -0
- half_orm_gen/tools.py +66 -0
- half_orm_gen/version.txt +1 -0
- half_orm_gen-1.0.0a1.dist-info/METADATA +73 -0
- half_orm_gen-1.0.0a1.dist-info/RECORD +29 -0
- half_orm_gen-1.0.0a1.dist-info/WHEEL +5 -0
- half_orm_gen-1.0.0a1.dist-info/licenses/AUTHORS +3 -0
- half_orm_gen-1.0.0a1.dist-info/licenses/LICENSE +674 -0
- half_orm_gen-1.0.0a1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
FastAPI template strings for the generated api/app.py.
|
|
3
|
+
|
|
4
|
+
Used by `half_orm litestar generate --fastapi`.
|
|
5
|
+
Only auto-CRUD routes (CRUD_ACCESS) are supported; @api_* decorators are
|
|
6
|
+
Litestar-specific and are ignored in FastAPI mode.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from half_orm_gen.templates import CRUD_HELPERS, CRUD_MODULE_IMPORT
|
|
10
|
+
|
|
11
|
+
FRAMEWORK = 'fastapi'
|
|
12
|
+
|
|
13
|
+
HEADER = """\
|
|
14
|
+
# This file is generated by `half_orm litestar generate --fastapi`. Do not edit.
|
|
15
|
+
from typing import Optional, List, Any
|
|
16
|
+
import typing
|
|
17
|
+
import datetime
|
|
18
|
+
import uuid
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from contextlib import asynccontextmanager
|
|
22
|
+
|
|
23
|
+
cur_dir = os.path.dirname(os.path.abspath(__file__))
|
|
24
|
+
sys.path.insert(0, cur_dir)
|
|
25
|
+
par_dir = os.path.join(cur_dir, os.path.pardir)
|
|
26
|
+
sys.path.insert(0, par_dir)
|
|
27
|
+
|
|
28
|
+
from fastapi import FastAPI, APIRouter, HTTPException, Request
|
|
29
|
+
from pydantic import BaseModel
|
|
30
|
+
|
|
31
|
+
from {module} import ho_baseclasses
|
|
32
|
+
from {module} import ho_typeddicts
|
|
33
|
+
from {module} import MODEL
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
from api.custom.routes import router as _custom_router
|
|
37
|
+
_has_custom = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
_has_custom = False
|
|
40
|
+
|
|
41
|
+
router = APIRouter()
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
FOOTER = '''
|
|
46
|
+
_HO_WARN = """
|
|
47
|
+
======================================================================
|
|
48
|
+
halfORM DEV HELPERS ACTIVE — NOT FOR PRODUCTION
|
|
49
|
+
======================================================================
|
|
50
|
+
/ho_roles : exposes all declared roles (no authentication)
|
|
51
|
+
/ho_access : exposes the full access map filtered by role
|
|
52
|
+
_get_roles : bearer token used directly as a role name
|
|
53
|
+
(no signature verification)
|
|
54
|
+
ho_dev : super-role with full access to all resources
|
|
55
|
+
(Authorization: Bearer ho_dev)
|
|
56
|
+
|
|
57
|
+
Replace the Authorization middleware with a real JWT implementation
|
|
58
|
+
before deploying to production.
|
|
59
|
+
======================================================================
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
_HO_WARN_SHOWN = False
|
|
63
|
+
|
|
64
|
+
@asynccontextmanager
|
|
65
|
+
async def _ho_lifespan(app: FastAPI):
|
|
66
|
+
global _HO_WARN_SHOWN
|
|
67
|
+
import sys
|
|
68
|
+
await MODEL.aconnect()
|
|
69
|
+
if MODEL._production_mode:
|
|
70
|
+
raise RuntimeError(
|
|
71
|
+
"halfORM DEV HELPERS are active (ho_roles, ho_access, _get_roles fallback). "
|
|
72
|
+
"These routes and the bearer-token-as-role fallback are not safe for production. "
|
|
73
|
+
"Secure or remove them before deploying."
|
|
74
|
+
)
|
|
75
|
+
if not _HO_WARN_SHOWN:
|
|
76
|
+
print(_HO_WARN, file=sys.stderr, flush=True)
|
|
77
|
+
_HO_WARN_SHOWN = True
|
|
78
|
+
yield
|
|
79
|
+
|
|
80
|
+
application = FastAPI(lifespan=_ho_lifespan{openapi_config})
|
|
81
|
+
application.include_router(router)
|
|
82
|
+
if _has_custom:
|
|
83
|
+
application.include_router(_custom_router)
|
|
84
|
+
'''
|
|
85
|
+
|
|
86
|
+
OPENAPI_CONFIG = """, title="{title}", version="{version}" """
|
|
87
|
+
|
|
88
|
+
HO_ACCESS_ROUTE = (
|
|
89
|
+
'\n_STATIC_ACCESS_MAP = {json_str}\n'
|
|
90
|
+
'\n_ACCESS_MAP = get_access_map()\n\n'
|
|
91
|
+
'@router.get("{version_prefix}/ho_access")\n'
|
|
92
|
+
'async def _crud_access_map(request: Request) -> dict:\n'
|
|
93
|
+
' authorized_roles = _get_roles(request)\n'
|
|
94
|
+
' return _filter_access_for_roles(_ACCESS_MAP, authorized_roles)\n'
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
HO_ROLES_ROUTE = (
|
|
98
|
+
'\n_ROLES = {roles_json}\n\n'
|
|
99
|
+
'@router.get("{version_prefix}/ho_roles")\n'
|
|
100
|
+
'async def _crud_roles_list() -> list:\n'
|
|
101
|
+
' return _ROLES\n'
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
WS_HELPERS = """
|
|
105
|
+
import json as _json
|
|
106
|
+
from fastapi import WebSocket as _WS, WebSocketDisconnect as _WSD
|
|
107
|
+
|
|
108
|
+
class _ConnectionManager:
|
|
109
|
+
def __init__(self):
|
|
110
|
+
self._sockets: set = set()
|
|
111
|
+
|
|
112
|
+
async def connect(self, ws: _WS) -> None:
|
|
113
|
+
await ws.accept()
|
|
114
|
+
self._sockets.add(ws)
|
|
115
|
+
|
|
116
|
+
def disconnect(self, ws: _WS) -> None:
|
|
117
|
+
self._sockets.discard(ws)
|
|
118
|
+
|
|
119
|
+
async def broadcast(self, message: dict) -> None:
|
|
120
|
+
dead = set()
|
|
121
|
+
for s in set(self._sockets):
|
|
122
|
+
try:
|
|
123
|
+
await s.send_text(_json.dumps(message, default=str))
|
|
124
|
+
except Exception:
|
|
125
|
+
dead.add(s)
|
|
126
|
+
self._sockets -= dead
|
|
127
|
+
|
|
128
|
+
_manager = _ConnectionManager()
|
|
129
|
+
|
|
130
|
+
@router.websocket("{version_prefix}/ws")
|
|
131
|
+
async def _ws_handler(ws: _WS) -> None:
|
|
132
|
+
await _manager.connect(ws)
|
|
133
|
+
try:
|
|
134
|
+
while True:
|
|
135
|
+
await ws.receive_text()
|
|
136
|
+
except _WSD:
|
|
137
|
+
pass
|
|
138
|
+
finally:
|
|
139
|
+
_manager.disconnect(ws)
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
CRUD_GET_LIST = """
|
|
143
|
+
@router.get("{path}", description="{access_description}")
|
|
144
|
+
async def {handler_name}(
|
|
145
|
+
request: Request,
|
|
146
|
+
{filter_params} fields: Optional[List[str]] = None,
|
|
147
|
+
limit: Optional[int] = None,
|
|
148
|
+
offset: Optional[int] = None,
|
|
149
|
+
) -> list:
|
|
150
|
+
api_excluded = getattr({module_alias}, 'API_EXCLUDED_FIELDS', [])
|
|
151
|
+
roles = _get_roles(request)
|
|
152
|
+
filter_kwargs = {{{filter_dict}}}
|
|
153
|
+
role_filter = _get_role_filter(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "GET", roles)
|
|
154
|
+
authorized = _effective_out_fields(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "GET", roles, api_excluded)
|
|
155
|
+
if fields:
|
|
156
|
+
projection = [f for f in fields if not authorized or f in authorized]
|
|
157
|
+
else:
|
|
158
|
+
projection = authorized
|
|
159
|
+
return await {module_alias}.{class_name}(**{{**filter_kwargs, **role_filter}}).ho_aselect(
|
|
160
|
+
*projection, limit=limit, offset=offset
|
|
161
|
+
)
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
CRUD_GET_ONE = """
|
|
165
|
+
@router.get("{path}/{{id}}", description="{access_description}")
|
|
166
|
+
async def {handler_name}_get(
|
|
167
|
+
request: Request,
|
|
168
|
+
id: {pk_py_type},
|
|
169
|
+
) -> dict:
|
|
170
|
+
api_excluded = getattr({module_alias}, 'API_EXCLUDED_FIELDS', [])
|
|
171
|
+
roles = _get_roles(request)
|
|
172
|
+
role_filter = _get_role_filter(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "GET", roles)
|
|
173
|
+
authorized = _effective_out_fields(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "GET", roles, api_excluded)
|
|
174
|
+
rows = await {module_alias}.{class_name}({pk_instance_filter}, **role_filter).ho_aselect(*authorized)
|
|
175
|
+
if not rows:
|
|
176
|
+
raise HTTPException(status_code=404)
|
|
177
|
+
return rows[0]
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
CRUD_POST = """
|
|
181
|
+
@router.post("{path}", description="{access_description}")
|
|
182
|
+
async def {handler_name}_create(
|
|
183
|
+
request: Request,
|
|
184
|
+
data: {in_typedict},
|
|
185
|
+
) -> dict:
|
|
186
|
+
api_excluded = getattr({module_alias}, 'API_EXCLUDED_FIELDS', [])
|
|
187
|
+
in_fields = _effective_in_fields(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "POST", _get_roles(request), api_excluded)
|
|
188
|
+
payload = {{k: v for k, v in data.model_dump(exclude_none=True).items() if not in_fields or k in in_fields}}
|
|
189
|
+
result = await {module_alias}.{class_name}(**payload).ho_ainsert()
|
|
190
|
+
await _manager.broadcast({{"event": "create", "resource": "{resource}", "id": {pk_broadcast_expr}}})
|
|
191
|
+
return result
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
CRUD_PUT = """
|
|
195
|
+
@router.put("{path}/{{id}}", description="{access_description}")
|
|
196
|
+
async def {handler_name}_update(
|
|
197
|
+
request: Request,
|
|
198
|
+
id: {pk_py_type},
|
|
199
|
+
data: {in_typedict},
|
|
200
|
+
) -> dict:
|
|
201
|
+
api_excluded = getattr({module_alias}, 'API_EXCLUDED_FIELDS', [])
|
|
202
|
+
in_fields = _effective_in_fields(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "PUT", _get_roles(request), api_excluded)
|
|
203
|
+
payload = {{k: v for k, v in data.model_dump(exclude_none=True).items() if not in_fields or k in in_fields}}
|
|
204
|
+
authorized = _effective_out_fields(getattr({module_alias}, 'CRUD_ACCESS', {{}}), "PUT", _get_roles(request), api_excluded)
|
|
205
|
+
result = await {module_alias}.{class_name}({pk_instance_filter}).ho_aupdate(*(authorized or ['*']), **payload)
|
|
206
|
+
if not result:
|
|
207
|
+
raise HTTPException(status_code=404)
|
|
208
|
+
await _manager.broadcast({{"event": "update", "resource": "{resource}", "id": id}})
|
|
209
|
+
return result[0]
|
|
210
|
+
"""
|
|
211
|
+
|
|
212
|
+
CRUD_DELETE = """
|
|
213
|
+
@router.delete("{path}/{{id}}", description="{access_description}")
|
|
214
|
+
async def {handler_name}_delete(
|
|
215
|
+
request: Request,
|
|
216
|
+
id: {pk_py_type},
|
|
217
|
+
) -> None:
|
|
218
|
+
await _ws_broadcast_cascade(
|
|
219
|
+
{module_alias}.{class_name}({pk_instance_filter}), "{resource}", id
|
|
220
|
+
)
|
|
221
|
+
result = await {module_alias}.{class_name}({pk_instance_filter}).ho_adelete('*')
|
|
222
|
+
if not result:
|
|
223
|
+
raise HTTPException(status_code=404)
|
|
224
|
+
await _manager.broadcast({{"event": "delete", "resource": "{resource}", "id": id}})
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
WS_CASCADE_HELPER = """
|
|
228
|
+
_WS_RMAP: dict = {{
|
|
229
|
+
{resource_entries}
|
|
230
|
+
}}
|
|
231
|
+
|
|
232
|
+
async def _ws_broadcast_cascade(inst, resource: str, pk_val, _seen: set | None = None) -> None:
|
|
233
|
+
if _seen is None:
|
|
234
|
+
_seen = set()
|
|
235
|
+
_key = (resource, str(pk_val))
|
|
236
|
+
if _key in _seen:
|
|
237
|
+
return
|
|
238
|
+
_seen.add(_key)
|
|
239
|
+
for _fk in inst._ho_fkeys.values():
|
|
240
|
+
if not _fk.is_reverse or len(_fk.fk_names) != 1:
|
|
241
|
+
continue
|
|
242
|
+
_fk_field = _fk.fk_names[0]
|
|
243
|
+
_fqtn = _fk.remote['fqtn']
|
|
244
|
+
_r = f"{{_fqtn[0].replace('.', '_')}}/{{_fqtn[1]}}"
|
|
245
|
+
if _r not in _WS_RMAP:
|
|
246
|
+
continue
|
|
247
|
+
_cls, _pk = _WS_RMAP[_r]
|
|
248
|
+
for _row in await _cls(**{{_fk_field: pk_val}}).ho_aselect(_pk):
|
|
249
|
+
_rid = _row[_pk]
|
|
250
|
+
await _ws_broadcast_cascade(_cls(**{{_pk: _rid}}), _r, _rid, _seen)
|
|
251
|
+
await _manager.broadcast({{"event": "delete", "resource": _r, "id": _rid}})
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def typedict_block(class_name: str, field_names: list, all_fields: dict) -> str:
|
|
256
|
+
from half_orm_gen.crud_routes import _py_type_str
|
|
257
|
+
lines = [f'class {class_name}(BaseModel):']
|
|
258
|
+
valid = [(f, all_fields[f]) for f in field_names if f in all_fields]
|
|
259
|
+
if not valid:
|
|
260
|
+
lines.append(' pass')
|
|
261
|
+
else:
|
|
262
|
+
for fname, fobj in valid:
|
|
263
|
+
lines.append(f' {fname}: Optional[{_py_type_str(fobj.py_type)}] = None')
|
|
264
|
+
return '\n'.join(lines)
|
half_orm_gen/tools.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Decorators for exposing halfORM class methods as Litestar API routes.
|
|
3
|
+
|
|
4
|
+
Usage in a halfORM relation class::
|
|
5
|
+
|
|
6
|
+
from half_orm_gen import tools
|
|
7
|
+
|
|
8
|
+
class MyRelation(MODEL.get_relation_class('schema.table')):
|
|
9
|
+
@tools.api_get('/items/{id: uuid}', guards=['connected'])
|
|
10
|
+
async def get_item(self, request: "Request"):
|
|
11
|
+
...
|
|
12
|
+
|
|
13
|
+
@tools.api_post('/items', guards=['connected'])
|
|
14
|
+
async def create_item(self, request: "Request"):
|
|
15
|
+
...
|
|
16
|
+
|
|
17
|
+
The decorated methods are discovered by ``half_orm litestar generate`` which
|
|
18
|
+
produces the ``api/main.py`` Litestar application file.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import inspect
|
|
22
|
+
from functools import wraps
|
|
23
|
+
from typing import Callable
|
|
24
|
+
from litestar import get, post, put, delete, patch
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def create_api_decorator(http_method: str, litestar_decorator: Callable):
|
|
28
|
+
"""Create an api_* decorator that mirrors the signature of the given Litestar decorator."""
|
|
29
|
+
litestar_sig = inspect.signature(litestar_decorator)
|
|
30
|
+
|
|
31
|
+
def api_decorator(*args, **kwargs):
|
|
32
|
+
bound_args = litestar_sig.bind(*args, **kwargs)
|
|
33
|
+
bound_args.apply_defaults()
|
|
34
|
+
|
|
35
|
+
def wrapper(func: Callable) -> Callable:
|
|
36
|
+
@wraps(func)
|
|
37
|
+
def inner(*func_args, **func_kwargs):
|
|
38
|
+
return func(*func_args, **func_kwargs)
|
|
39
|
+
|
|
40
|
+
inner.is_api_route = True
|
|
41
|
+
inner.http_method = http_method
|
|
42
|
+
inner.litestar_params = dict(bound_args.arguments)
|
|
43
|
+
inner.metadata = {
|
|
44
|
+
'signature': inspect.signature(func),
|
|
45
|
+
'documentation': func.__doc__ or '',
|
|
46
|
+
'litestar_decorator': litestar_decorator,
|
|
47
|
+
'bound_arguments': bound_args,
|
|
48
|
+
}
|
|
49
|
+
return inner
|
|
50
|
+
|
|
51
|
+
return wrapper
|
|
52
|
+
|
|
53
|
+
api_decorator.__signature__ = litestar_sig
|
|
54
|
+
api_decorator.__name__ = f"api_{http_method.lower()}"
|
|
55
|
+
api_decorator.__doc__ = (
|
|
56
|
+
f"API decorator for {http_method} routes. "
|
|
57
|
+
f"Accepts the same arguments as litestar.{http_method.lower()}."
|
|
58
|
+
)
|
|
59
|
+
return api_decorator
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
api_get = create_api_decorator('GET', get)
|
|
63
|
+
api_post = create_api_decorator('POST', post)
|
|
64
|
+
api_put = create_api_decorator('PUT', put)
|
|
65
|
+
api_delete = create_api_decorator('DELETE', delete)
|
|
66
|
+
api_patch = create_api_decorator('PATCH', patch)
|
half_orm_gen/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.0-a1
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: half_orm_gen
|
|
3
|
+
Version: 1.0.0a1
|
|
4
|
+
Summary: API and frontend backoffice generation for halfORM projects.
|
|
5
|
+
Author-email: Joël Maïzi <joel.maizi@collorg.org>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/half-orm/half-orm-gen
|
|
8
|
+
Keywords: litestar,half-orm,rest,api,postgresql,code-generation,asgi,orm,svelte,angular
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
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 :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
License-File: AUTHORS
|
|
22
|
+
Requires-Dist: half-orm<1.1.0,>=1.0.0rc13
|
|
23
|
+
Requires-Dist: half-orm-dev<1.1.0,>=1.0.0a32
|
|
24
|
+
Requires-Dist: litestar>=2.0.0
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# half-orm-gen
|
|
28
|
+
|
|
29
|
+
A [halfORM](https://github.com/half-orm/half-orm) extension that generates a
|
|
30
|
+
[Litestar](https://litestar.dev) or [FastAPI](https://fastapi.tiangolo.com) REST API
|
|
31
|
+
**and** a frontend backoffice ([SvelteKit 5](https://svelte.dev) or
|
|
32
|
+
[Angular](https://angular.dev)) from your halfORM project.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install half-orm-gen
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Litestar
|
|
46
|
+
half_orm gen api --litestar
|
|
47
|
+
litestar --app api.app:application run --reload
|
|
48
|
+
|
|
49
|
+
# FastAPI
|
|
50
|
+
half_orm gen api --fastapi
|
|
51
|
+
uvicorn api.app:application --reload
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Frontend backoffice
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# SvelteKit 5
|
|
60
|
+
half_orm gen frontend --svelte
|
|
61
|
+
cd frontend/svelte && npm install && npm run dev
|
|
62
|
+
|
|
63
|
+
# Angular
|
|
64
|
+
half_orm gen frontend --angular
|
|
65
|
+
cd frontend/angular && npm install && npm start
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## See also
|
|
71
|
+
|
|
72
|
+
- [half-orm](https://github.com/half-orm/half-orm) — the PostgreSQL ORM at the core
|
|
73
|
+
- [half-orm-dev](https://github.com/half-orm/half-orm-dev) — the development framework
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
half_orm_gen/__init__.py,sha256=gsIzNnl38-e4XqYc4ud04wZTvebMnzfHer8K5ISvVi0,448
|
|
2
|
+
half_orm_gen/api_routes.py,sha256=wm90JWm5U2lTabfIFXXBKkIFvutjMb6I6qOkanNgdk4,7954
|
|
3
|
+
half_orm_gen/cli_extension.py,sha256=BNALyL3aR3jkPDB9OWIvc5BOUSBnSkYul9tcV7ParXQ,6266
|
|
4
|
+
half_orm_gen/crud_routes.py,sha256=GwF7mTXSeXjmZ7TxDntMAn0uEzCStiK3MSHKhssP1W8,20641
|
|
5
|
+
half_orm_gen/generate.py,sha256=gPlfFuUIbwh47QHadehkbTNYe0f-F02i5y4oI5aoaKM,4094
|
|
6
|
+
half_orm_gen/scaffold.py,sha256=iVRk81YoylJeeQJ9yz56KpzXXqTkJIYrGzC_LLeMCgM,1303
|
|
7
|
+
half_orm_gen/templates.py,sha256=Zdfi_SDnZVOzhiTM7myoDO5bhbpyeaxUcVfkWimr_Os,15542
|
|
8
|
+
half_orm_gen/templates_fastapi.py,sha256=yYauOFha1ehsQyaKbnnnS-wEHo5l59uOmQj1OqNQ6g0,9172
|
|
9
|
+
half_orm_gen/tools.py,sha256=0e557C-rifjurzlOyD2B8UHO7eMrAkaGXfYFCpow5OY,2278
|
|
10
|
+
half_orm_gen/version.txt,sha256=V7I2VeuW_SCn04qwchb5-EAwEtzMEiDRAlYord5iGng,9
|
|
11
|
+
half_orm_gen/gen_app/__init__.py,sha256=OW0tcuXBQUdHwuDBVj7Bo0Xse8U4f3tgukBfhgxsZsM,730
|
|
12
|
+
half_orm_gen/gen_app/angular.py,sha256=6FLSBV9CJV5a-l1tcngANkzKt6-yxECHtjPAmfLJejs,66718
|
|
13
|
+
half_orm_gen/gen_app/svelte.py,sha256=upqduNPimoPfbsRw66H0V93fG8N-lilvvb3jIuGhrj0,50823
|
|
14
|
+
half_orm_gen/gen_store/__init__.py,sha256=Ww3v9nuZNUI-GR5FyzmjSrokRydDt-NhNmKoR11Zd24,844
|
|
15
|
+
half_orm_gen/gen_store/base.py,sha256=mBkoF8X4pEDv2n3ol9-Nqyu3LVGvgOEGDZej_Qv9gbU,3390
|
|
16
|
+
half_orm_gen/gen_store/svelte.py,sha256=G-qIqjEkAbAO3reZL6J-bIK_OEqUwmhf6f64BwWB3_8,12207
|
|
17
|
+
half_orm_gen/scaffolding/api_init.py,sha256=VLdSX8mfJ3P9ul_XOw50g0TzapwfFtErpVIus8ET4pA,13
|
|
18
|
+
half_orm_gen/scaffolding/custom_authorization.py,sha256=3V7BtO5EBE0RxVZ7hUpzyLOAEcNhmWNUZuZfRIQykxg,1435
|
|
19
|
+
half_orm_gen/scaffolding/custom_init.py,sha256=mzymXvpfT4uM2G226Cc48FDa2dY0zbvNO1KC4QwVwUw,20
|
|
20
|
+
half_orm_gen/scaffolding/custom_middlewares_init.py,sha256=oWxx6F34oVC7Hj7qGIBxy3QQcZl7hrGRMIItMeq-XzY,395
|
|
21
|
+
half_orm_gen/scaffolding/custom_routes.py,sha256=YomUbbssjGKhD5wgQoUvW8fEkZcY1LyLa7eH3SEH-PA,362
|
|
22
|
+
half_orm_gen/scaffolding/guards.py,sha256=Z48COmmfXcBDcL4AgwUdofuuXvj8oud5Qi5ZYRTnbw8,1257
|
|
23
|
+
half_orm_gen/scaffolding/roles_core.py,sha256=V6mO33e_DXaCBK4964B4nr3XUfAj7WtDjF4we1k3Wyk,2048
|
|
24
|
+
half_orm_gen-1.0.0a1.dist-info/licenses/AUTHORS,sha256=EaXsDPeylv3aZMFUOd5OJAqS3ZNCIKlj5nTQcjaSZ48,108
|
|
25
|
+
half_orm_gen-1.0.0a1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
26
|
+
half_orm_gen-1.0.0a1.dist-info/METADATA,sha256=oxg1N98PuYqoTLw-ZFM6axODhkOqG3WSJR6eHbcvDB4,2036
|
|
27
|
+
half_orm_gen-1.0.0a1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
28
|
+
half_orm_gen-1.0.0a1.dist-info/top_level.txt,sha256=bF9PTTtutOHtVCTiw62Ih0hqk4M_Ze9smNXdh32i4O0,13
|
|
29
|
+
half_orm_gen-1.0.0a1.dist-info/RECORD,,
|