sqlmodelclassapi 0.1.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.
File without changes
@@ -0,0 +1,160 @@
1
+ from classapi import BaseView
2
+ import fastapi as _fastapi
3
+ from sqlmodel import Session
4
+
5
+ def make_session_view(engine = None):
6
+ class SessionView(BaseView):
7
+ def __init__(self):
8
+ super().__init__()
9
+ self.session = None # will be set in the endpoint wrapper
10
+
11
+ def _add_route(self, app: _fastapi.FastAPI, path: str = "/", **kwargs):
12
+ """Register handlers on `app` forwarding any FastAPI-compatible kwargs.
13
+
14
+ `kwargs` are passed directly to `app.add_api_route`, so you can provide
15
+ `response_model`, `dependencies`, `status_code`, etc.
16
+ """
17
+ # create FastAPI-compatible endpoint wrappers that preserve the
18
+ # original method signature (excluding `self`) by setting
19
+ # `__signature__` on the wrapper. FastAPI uses that for dependency
20
+ # injection and parameter parsing.
21
+ def _make_endpoint(method_name, http_methods):
22
+ import inspect
23
+ func = getattr(self, method_name)
24
+ sig_func = inspect.signature(func)
25
+ params_func = list(sig_func.parameters.values())
26
+ if params_func and params_func[0].name == "self":
27
+ params_func = params_func[1:]
28
+ # exclude *args and **kwargs from the public signature
29
+ params_func = [p for p in params_func if p.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)]
30
+
31
+ # gather pre handlers: specific pre_<method> and general pre_process
32
+ pre_specific = getattr(self, f"pre_{method_name}", None)
33
+ pre_general = getattr(self, "pre_process", None)
34
+
35
+ params_pre_specific = []
36
+ params_pre_general = []
37
+ if pre_specific is not None:
38
+ sig_pre = inspect.signature(pre_specific)
39
+ params_pre_specific = list(sig_pre.parameters.values())
40
+ if params_pre_specific and params_pre_specific[0].name == "self":
41
+ params_pre_specific = params_pre_specific[1:]
42
+ # exclude *args and **kwargs from the public signature
43
+ params_pre_specific = [p for p in params_pre_specific if p.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)]
44
+ # also filter accidental plain 'args'/'kwargs' parameters
45
+ params_pre_specific = [p for p in params_pre_specific if p.name not in ("args", "kwargs")]
46
+ if pre_general is not None:
47
+ sig_pre_g = inspect.signature(pre_general)
48
+ params_pre_general = list(sig_pre_g.parameters.values())
49
+ if params_pre_general and params_pre_general[0].name == "self":
50
+ params_pre_general = params_pre_general[1:]
51
+ # exclude *args and **kwargs from the public signature
52
+ params_pre_general = [p for p in params_pre_general if p.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD)]
53
+ # also filter accidental plain 'args'/'kwargs' parameters
54
+ params_pre_general = [p for p in params_pre_general if p.name not in ("args", "kwargs")]
55
+
56
+ # merge params: general pre, specific pre, then func params; skip duplicates by name
57
+ seen = set()
58
+ merged = []
59
+ for p in params_pre_general + params_pre_specific + params_func:
60
+ if p.name not in seen:
61
+ merged.append(p)
62
+ seen.add(p.name)
63
+
64
+ # Reorder merged params to satisfy Python's parameter ordering rules:
65
+ # positional-only, positional-or-keyword, var-positional, keyword-only, var-keyword
66
+ posonly = []
67
+ pos_or_kw = []
68
+ var_pos = None
69
+ kw_only = []
70
+ var_kw = None
71
+ for p in merged:
72
+ if p.kind == inspect.Parameter.POSITIONAL_ONLY:
73
+ posonly.append(p)
74
+ elif p.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD:
75
+ pos_or_kw.append(p)
76
+ elif p.kind == inspect.Parameter.VAR_POSITIONAL:
77
+ if var_pos is None:
78
+ var_pos = p
79
+ elif p.kind == inspect.Parameter.KEYWORD_ONLY:
80
+ kw_only.append(p)
81
+ elif p.kind == inspect.Parameter.VAR_KEYWORD:
82
+ if var_kw is None:
83
+ var_kw = p
84
+
85
+ # Ensure parameters without defaults come before those with defaults
86
+ def split_by_default(params):
87
+ no_def = [p for p in params if p.default is inspect._empty]
88
+ has_def = [p for p in params if p.default is not inspect._empty]
89
+ return no_def + has_def
90
+
91
+ posonly = split_by_default(posonly)
92
+ pos_or_kw = split_by_default(pos_or_kw)
93
+ kw_only = split_by_default(kw_only)
94
+
95
+ ordered = posonly + pos_or_kw
96
+ if var_pos is not None:
97
+ ordered.append(var_pos)
98
+ ordered += kw_only
99
+ if var_kw is not None:
100
+ ordered.append(var_kw)
101
+
102
+ # normalize parameters: if a parameter has no annotation but its default
103
+ # is an `Annotated[T, metadata...]` object (commonly used incorrectly
104
+ # as a default), extract the annotation and the first metadata item
105
+ # (e.g. Header()) and set them on the parameter so FastAPI detects it.
106
+ import typing
107
+ normalized = []
108
+ for p in ordered:
109
+ ann = p.annotation
110
+ default = p.default
111
+ if (ann is inspect._empty and default is not inspect._empty
112
+ and typing.get_origin(default) is typing.Annotated):
113
+ args = typing.get_args(default)
114
+ if args:
115
+ new_ann = args[0]
116
+ new_default = args[1] if len(args) > 1 else inspect._empty
117
+ p = inspect.Parameter(p.name, p.kind, default=new_default, annotation=new_ann)
118
+ normalized.append(p)
119
+
120
+ new_sig = inspect.Signature(parameters=normalized)
121
+
122
+ def endpoint(**kwargs):
123
+ if engine is None:
124
+ raise RuntimeError(
125
+ "SessionView has no engine configured. "
126
+ "Create it with make_session_view(engine=your_engine)."
127
+ )
128
+ with Session(engine) as session:
129
+ self.session = session # make session available as an instance attribute for handlers and pre-processors
130
+ try:
131
+ # call general pre_process with its subset
132
+ if pre_general is not None:
133
+ pre_g_kwargs = {k: v for k, v in kwargs.items() if k in {p.name for p in params_pre_general}}
134
+ pre_general(**pre_g_kwargs)
135
+ # call specific pre_<method> with its subset
136
+ if pre_specific is not None:
137
+ pre_s_kwargs = {k: v for k, v in kwargs.items() if k in {p.name for p in params_pre_specific}}
138
+ pre_specific(**pre_s_kwargs)
139
+ # call actual handler with its subset
140
+ func_kwargs = {k: v for k, v in kwargs.items() if k in {p.name for p in params_func}}
141
+ return func(**func_kwargs)
142
+ finally:
143
+ self.session = None
144
+
145
+ endpoint.__signature__ = new_sig
146
+ endpoint.__name__ = f"{self.__class__.__name__}_{method_name}_endpoint"
147
+ return endpoint
148
+
149
+ if hasattr(self, "get") and ("GET" in self.methods or "get" in self.methods):
150
+ app.add_api_route(path, _make_endpoint("get", ["GET"]), methods=["GET"], **kwargs)
151
+ if hasattr(self, "post") and ("POST" in self.methods or "post" in self.methods):
152
+ app.add_api_route(path, _make_endpoint("post", ["POST"]), methods=["POST"], **kwargs)
153
+ if hasattr(self, "put") and ("PUT" in self.methods or "put" in self.methods):
154
+ app.add_api_route(path, _make_endpoint("put", ["PUT"]), methods=["PUT"], **kwargs)
155
+ if hasattr(self, "delete") and ("DELETE" in self.methods or "delete" in self.methods):
156
+ app.add_api_route(path, _make_endpoint("delete", ["DELETE"]), methods=["DELETE"], **kwargs)
157
+ if hasattr(self, "patch") and ("PATCH" in self.methods or "patch" in self.methods):
158
+ app.add_api_route(path, _make_endpoint("patch", ["PATCH"]), methods=["PATCH"], **kwargs)
159
+
160
+ return SessionView
@@ -0,0 +1,79 @@
1
+ Metadata-Version: 2.4
2
+ Name: sqlmodelclassapi
3
+ Version: 0.1.0
4
+ Summary: Add your description here
5
+ Requires-Python: >=3.12
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: classapi>=0.1.0.1
8
+ Requires-Dist: sqlmodel>=0.0.34
9
+
10
+ # SQLModel support for classApi
11
+
12
+ This project provides a simple way to use a SQLModel `Session` inside your classApi views.
13
+
14
+ First, create your SQLModel engine. Here is a basic example:
15
+
16
+ ```py
17
+ # engine.py
18
+ from sqlalchemy import create_engine
19
+ from sqlmodel import SQLModel
20
+
21
+ from .model import User # In this example, User has 3 fields: id, name, and email
22
+
23
+
24
+ sqlite_file_name = "database.db"
25
+ sqlite_url = f"sqlite:///{sqlite_file_name}"
26
+
27
+ engine = create_engine(sqlite_url, echo=True)
28
+
29
+ def create_db_and_tables():
30
+ SQLModel.metadata.create_all(engine)
31
+ ```
32
+
33
+ Then create a new base view using `make_session_view`:
34
+
35
+ ```py
36
+ # engine.py
37
+ from sqlalchemy import create_engine
38
+ from sqlmodel import SQLModel
39
+
40
+ from sqlmodelclassapi.main import make_session_view
41
+ from .model import User
42
+
43
+
44
+ sqlite_file_name = "database.db"
45
+ sqlite_url = f"sqlite:///{sqlite_file_name}"
46
+
47
+ engine = create_engine(sqlite_url, echo=True)
48
+
49
+ def create_db_and_tables():
50
+ SQLModel.metadata.create_all(engine)
51
+
52
+ SessionView = make_session_view(engine=engine) # New base view with session support
53
+ ```
54
+
55
+ Now, to create a view, inherit from `SessionView` instead of `BaseView`:
56
+
57
+ ```py
58
+ # views.py
59
+ class ExampleSessionView(SessionView):
60
+ methods = ["GET", "POST"]
61
+
62
+ def get(self, *args):
63
+ statement = select(User)
64
+ results = self.session.exec(statement).all()
65
+ return [
66
+ {"id": user.id, "name": user.name, "email": user.email}
67
+ for user in results
68
+ ]
69
+
70
+ def post(self, name: str):
71
+ new_user = User(name=name, email=f"{name.lower()}@example.com")
72
+ self.session.add(new_user)
73
+ self.session.commit()
74
+ self.session.refresh(new_user)
75
+
76
+ return {"message": f"User '{name}' created successfully!", "user": new_user}
77
+ ```
78
+
79
+ With this setup, all operations in your request run inside the same session, including `pre_{method}` hooks.
@@ -0,0 +1,6 @@
1
+ sqlmodelclassapi/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ sqlmodelclassapi/main.py,sha256=TT8wv25l58E1XxucEbjQbYZsUJYVgGpd3FMtEj5e-14,9348
3
+ sqlmodelclassapi-0.1.0.dist-info/METADATA,sha256=bNv-QR7VZI5ZQgF5QGMNyEQOVOBPwoJlmF-rs6Jilow,2176
4
+ sqlmodelclassapi-0.1.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
5
+ sqlmodelclassapi-0.1.0.dist-info/top_level.txt,sha256=HI1EUMdAJr7kYZCahTAk9haV4MmJRyoe1KlIBUO1HWw,17
6
+ sqlmodelclassapi-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ sqlmodelclassapi