sqlobjects 1.6.0__tar.gz → 1.8.0__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.
Files changed (81) hide show
  1. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/CHANGELOG.md +27 -0
  2. {sqlobjects-1.6.0/sqlobjects.egg-info → sqlobjects-1.8.0}/PKG-INFO +1 -1
  3. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/pyproject.toml +1 -1
  4. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/__init__.py +4 -0
  5. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/function.py +45 -8
  6. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/types/comparators.py +8 -2
  7. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/queries/executor.py +30 -0
  8. sqlobjects-1.8.0/sqlobjects/sql_logging.py +156 -0
  9. {sqlobjects-1.6.0 → sqlobjects-1.8.0/sqlobjects.egg-info}/PKG-INFO +1 -1
  10. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects.egg-info/SOURCES.txt +1 -0
  11. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/LICENSE +0 -0
  12. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/README.md +0 -0
  13. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/01-database-session-guide.md +0 -0
  14. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/02-model-definition-guide.md +0 -0
  15. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/03-query-operations-guide.md +0 -0
  16. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/04-crud-operations-guide.md +0 -0
  17. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/05-relationships-guide.md +0 -0
  18. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/06-validation-signals-guide.md +0 -0
  19. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/07-performance-guide.md +0 -0
  20. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/docs/rules/README.md +0 -0
  21. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/setup.cfg +0 -0
  22. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/_install_rules.py +0 -0
  23. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/cascade.py +0 -0
  24. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/contrib/__init__.py +0 -0
  25. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/contrib/asgi.py +0 -0
  26. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/contrib/fastapi.py +0 -0
  27. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/database/__init__.py +0 -0
  28. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/database/config.py +0 -0
  29. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/database/manager.py +0 -0
  30. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/exceptions.py +0 -0
  31. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/__init__.py +0 -0
  32. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/aggregate.py +0 -0
  33. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/base.py +0 -0
  34. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/cte.py +0 -0
  35. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/explain.py +0 -0
  36. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/mixins.py +0 -0
  37. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/scalar.py +0 -0
  38. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/subquery.py +0 -0
  39. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/terminal.py +0 -0
  40. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/expressions/window.py +0 -0
  41. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/__init__.py +0 -0
  42. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/core.py +0 -0
  43. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/functions.py +0 -0
  44. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/proxies.py +0 -0
  45. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/relations/__init__.py +0 -0
  46. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/relations/descriptors.py +0 -0
  47. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/relations/managers.py +0 -0
  48. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/relations/prefetch.py +0 -0
  49. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/relations/strategies.py +0 -0
  50. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/relations/utils.py +0 -0
  51. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/shortcuts.py +0 -0
  52. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/types/__init__.py +0 -0
  53. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/types/base.py +0 -0
  54. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/types/registry.py +0 -0
  55. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/fields/utils.py +0 -0
  56. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/internal/__init__.py +0 -0
  57. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/internal/operations.py +0 -0
  58. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/internal/results.py +0 -0
  59. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/metadata.py +0 -0
  60. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/mixins.py +0 -0
  61. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/model.py +0 -0
  62. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/objects/__init__.py +0 -0
  63. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/objects/bulk.py +0 -0
  64. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/objects/core.py +0 -0
  65. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/objects/upsert.py +0 -0
  66. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/queries/__init__.py +0 -0
  67. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/queries/builder.py +0 -0
  68. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/queries/dialect.py +0 -0
  69. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/queryset.py +0 -0
  70. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/session.py +0 -0
  71. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/signals.py +0 -0
  72. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/utils/__init__.py +0 -0
  73. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/utils/inspect.py +0 -0
  74. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/utils/naming.py +0 -0
  75. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/utils/pattern.py +0 -0
  76. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects/validators.py +0 -0
  77. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects.egg-info/dependency_links.txt +0 -0
  78. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects.egg-info/entry_points.txt +0 -0
  79. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects.egg-info/requires.txt +0 -0
  80. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/sqlobjects.egg-info/top_level.txt +0 -0
  81. {sqlobjects-1.6.0 → sqlobjects-1.8.0}/tests/test_config.py +0 -0
@@ -1,3 +1,30 @@
1
+ ## 1.8.0 (2026-03-26)
2
+
3
+ ### Feat
4
+
5
+ - **logging**: export SQLCallerFilter and get_caller_frame in public API
6
+ - **logging**: emit SQL log records in QueryExecutor
7
+ - **logging**: add SQLCallerFilter
8
+ - **logging**: add get_caller_frame() helper
9
+
10
+ ### Fix
11
+
12
+ - **logging**: eliminate isEnabledFor race and fix test name
13
+ - **logging**: restore logger level in test_no_log_when_logger_disabled
14
+ - **logging**: guard timing code with isEnabledFor check
15
+ - **logging**: simplify SQLCallerFilter extra_skip_packages handling
16
+ - **logging**: fix frame-skip edge cases and improve code quality
17
+
18
+ ## 1.7.0 (2026-03-26)
19
+
20
+ ### Feat
21
+
22
+ - **metadata**: add foreignkey() constraint builder
23
+
24
+ ### Fix
25
+
26
+ - **raw**: allow SA expressions as arguments in raw() methods
27
+
1
28
  ## 1.6.0 (2026-03-18)
2
29
 
3
30
  ### Feat
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlobjects
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading
5
5
  Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
6
6
  Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sqlobjects"
3
- version = "1.6.0"
3
+ version = "1.8.0"
4
4
  description = "Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -16,6 +16,7 @@ from .objects import (
16
16
  TransactionMode,
17
17
  )
18
18
  from .queryset import Q, QuerySet
19
+ from .sql_logging import SQLCallerFilter, get_caller_frame
19
20
 
20
21
 
21
22
  __version__ = "0.3.0"
@@ -41,4 +42,7 @@ __all__ = [
41
42
  "TransactionMode",
42
43
  "ErrorHandling",
43
44
  "ConflictResolution",
45
+ # SQL logging
46
+ "SQLCallerFilter",
47
+ "get_caller_frame",
44
48
  ]
@@ -183,7 +183,9 @@ class FunctionExpression:
183
183
 
184
184
  Args:
185
185
  sql: Raw SQL function name or expression
186
- *args: Arguments to pass to the function. Use ... (Ellipsis) as placeholder for current expression
186
+ *args: Arguments to pass to the function. Use ... (Ellipsis) as placeholder for current expression.
187
+ SQLAlchemy expressions (ColumnElement, FunctionExpression) are passed through as-is;
188
+ plain Python values are wrapped with literal().
187
189
  **kwargs: Additional keyword arguments passed to the function
188
190
 
189
191
  Returns:
@@ -198,12 +200,17 @@ class FunctionExpression:
198
200
  User.age.avg().raw('CUSTOM_FUNCTION', 'param1', ..., 'param2')
199
201
  # Generates: CUSTOM_FUNCTION('param1', avg(age), 'param2')
200
202
 
201
- # Only current expression
202
- User.age.raw('CUSTOM_FUNCTION')
203
- # Generates: CUSTOM_FUNCTION(age)
203
+ # Passing another SA expression as argument
204
+ User.name.raw('CUSTOM_FUNCTION', other_func_expr)
205
+ # Generates: CUSTOM_FUNCTION(name, other_func_expr)
204
206
  """
205
207
  from sqlalchemy import literal
206
208
 
209
+ def _to_sql_arg(arg):
210
+ if isinstance(arg, (ColumnElement, FunctionExpression)):
211
+ return arg
212
+ return literal(arg)
213
+
207
214
  # Check if ... (Ellipsis) is used as placeholder
208
215
  if ... in args:
209
216
  # Replace ... with current expression
@@ -212,12 +219,10 @@ class FunctionExpression:
212
219
  if arg is ...:
213
220
  all_args.append(self.expression)
214
221
  else:
215
- all_args.append(literal(arg))
222
+ all_args.append(_to_sql_arg(arg))
216
223
  else:
217
224
  # Default behavior: current expression as first argument
218
- all_args = [self.expression]
219
- for arg in args:
220
- all_args.append(literal(arg))
225
+ all_args = [self.expression] + [_to_sql_arg(arg) for arg in args]
221
226
 
222
227
  # Use func to create the raw function call
223
228
  raw_func = getattr(_sa_func, sql)
@@ -425,6 +430,38 @@ class _FuncWrapper:
425
430
 
426
431
  def nth_value(self, col: Any, n: int) -> NthValueFunction: ...
427
432
 
433
+ def raw(self, sql: str, *args) -> FunctionExpression: ...
434
+
435
+ def raw(self, sql: str, *args) -> "FunctionExpression":
436
+ """Call an arbitrary SQL function by name.
437
+
438
+ Unlike col.raw() / FunctionExpression.raw() which insert the current
439
+ expression as the first argument, this standalone form takes the function
440
+ name and all arguments explicitly.
441
+
442
+ Args:
443
+ sql: SQL function name (e.g. "ts_rank", "to_tsvector")
444
+ *args: Arguments passed to the function. SQLAlchemy expressions
445
+ (ColumnElement, FunctionExpression) are used as-is; plain
446
+ Python values are wrapped with literal().
447
+
448
+ Examples:
449
+ func.raw("ts_rank", DocumentIndexes.content_vector, query_vec)
450
+ # → ts_rank(document_indexes.content_vector, <query_vec>)
451
+
452
+ func.raw("to_tsvector", "'simple'::regconfig", "some text")
453
+ # → to_tsvector('simple'::regconfig, 'some text')
454
+ """
455
+ from sqlalchemy import literal
456
+
457
+ def _to_sql_arg(arg):
458
+ if isinstance(arg, (ColumnElement, FunctionExpression)):
459
+ return arg
460
+ return literal(arg)
461
+
462
+ sa_func = getattr(_sa_func, sql)
463
+ return FunctionExpression(sa_func(*[_to_sql_arg(a) for a in args]))
464
+
428
465
  def __getattr__(self, name: str) -> Any:
429
466
  """Delegate to SQLAlchemy func for all other functions."""
430
467
  return getattr(_sa_func, name)
@@ -50,6 +50,12 @@ class ComparatorMixin:
50
50
 
51
51
  def raw(self, sql: str, *args, **kwargs) -> FunctionExpression:
52
52
  from sqlalchemy import literal
53
+ from sqlalchemy.sql.elements import ColumnElement
54
+
55
+ def _to_sql_arg(arg):
56
+ if isinstance(arg, (ColumnElement, FunctionExpression)):
57
+ return arg
58
+ return literal(arg)
53
59
 
54
60
  if ... in args:
55
61
  all_args = []
@@ -57,9 +63,9 @@ class ComparatorMixin:
57
63
  if arg is ...:
58
64
  all_args.append(self)
59
65
  else:
60
- all_args.append(literal(arg))
66
+ all_args.append(_to_sql_arg(arg))
61
67
  else:
62
- all_args = [self] + [literal(arg) for arg in args]
68
+ all_args = [self] + [_to_sql_arg(arg) for arg in args]
63
69
  raw_func = getattr(func, sql)
64
70
  return FunctionExpression(raw_func(*all_args, **kwargs))
65
71
 
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
  import gc
3
+ import logging
4
+ import time
3
5
  from collections.abc import AsyncGenerator
4
6
  from typing import Any, TypeVar, overload
5
7
 
@@ -13,6 +15,9 @@ from sqlalchemy import (
13
15
  )
14
16
 
15
17
 
18
+ _sql_logger = logging.getLogger("sqlobjects.sql")
19
+
20
+
16
21
  _T = TypeVar("_T")
17
22
 
18
23
 
@@ -230,8 +235,33 @@ class QueryExecutor:
230
235
  else: # exists
231
236
  return False
232
237
 
238
+ # Compile SQL for logging only when the logger is active (avoids overhead)
239
+ _logging_active = _sql_logger.isEnabledFor(logging.DEBUG)
240
+ sql_str = ""
241
+ params: dict = {}
242
+ t0 = 0.0
243
+ if _logging_active:
244
+ try:
245
+ compiled = query.compile(
246
+ dialect=session.bind.dialect,
247
+ compile_kwargs={"literal_binds": False},
248
+ )
249
+ sql_str = str(compiled)
250
+ params = dict(compiled.params) if compiled.params else {}
251
+ except Exception:
252
+ sql_str = str(query)
253
+ params = {}
254
+ t0 = time.perf_counter()
255
+
233
256
  result = await session.execute(query)
234
257
 
258
+ if _logging_active:
259
+ duration_ms = (time.perf_counter() - t0) * 1000
260
+ _sql_logger.debug(
261
+ sql_str,
262
+ extra={"sql": sql_str, "params": params, "duration_ms": duration_ms},
263
+ )
264
+
235
265
  if query_type == "all":
236
266
  rows = result.fetchall()
237
267
  if model_class:
@@ -0,0 +1,156 @@
1
+ """SQL logging utilities for sqlobjects.
2
+
3
+ Provides get_caller_frame() to surface user-code caller information in SQL log
4
+ records, compatible with standard logging and loguru.
5
+
6
+ Filter strategy:
7
+ - Skips frames from site-packages (covers pip-installed sqlobjects/sqlalchemy)
8
+ - Skips frames whose module name starts with "sqlobjects." or "sqlalchemy."
9
+ (covers editable installs via `pip install -e .`)
10
+ - Skips frames from extra_skip_packages specified by the caller
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import inspect
16
+ import logging
17
+ import os
18
+
19
+
20
+ __all__ = ["get_caller_frame", "SQLCallerFilter"]
21
+
22
+ # Exact module names that are always considered internal
23
+ _INTERNAL_MODULES = {"sqlobjects", "sqlalchemy"}
24
+
25
+ # Module name prefixes that are always considered internal
26
+ _INTERNAL_PREFIXES = ("sqlobjects.", "sqlalchemy.")
27
+
28
+ # Absolute path of this file, used to skip itself reliably
29
+ _THIS_FILE = os.path.abspath(__file__)
30
+
31
+
32
+ def get_caller_frame(
33
+ extra_skip_packages: list[str] | None = None,
34
+ max_frames: int = 1,
35
+ ) -> str | list[str]:
36
+ """Inspect the call stack and return the first user-code frame(s).
37
+
38
+ Skips frames from:
39
+ - site-packages (pip install)
40
+ - sqlobjects.* and sqlalchemy.* modules (editable install)
41
+ - extra_skip_packages prefixes provided by the caller
42
+
43
+ Args:
44
+ extra_skip_packages: Additional module name prefixes to skip
45
+ (e.g. ["myapp.middleware"]). Matched against frame's __name__.
46
+ max_frames: How many user-code frames to return.
47
+ 1 returns a str; >1 returns a list[str].
48
+
49
+ Returns:
50
+ Frame string "path/to/file.py:lineno in funcname", or a list of such
51
+ strings when max_frames > 1.
52
+ """
53
+ skip_prefixes = _INTERNAL_PREFIXES
54
+ if extra_skip_packages:
55
+ skip_prefixes = skip_prefixes + tuple(extra_skip_packages)
56
+
57
+ frames: list[str] = []
58
+
59
+ for frame_info in inspect.stack():
60
+ filepath = frame_info.filename
61
+ module = frame_info.frame.f_globals.get("__name__", "")
62
+
63
+ # Skip site-packages frames (pip-installed third-party libs)
64
+ if "site-packages" in filepath:
65
+ continue
66
+
67
+ # Skip sqlobjects/sqlalchemy frames (editable install)
68
+ if module in _INTERNAL_MODULES or module.startswith(skip_prefixes):
69
+ continue
70
+
71
+ # Skip this helper file itself
72
+ if os.path.abspath(filepath) == _THIS_FILE:
73
+ continue
74
+
75
+ rel_path = _relative_path(filepath)
76
+ frames.append(f"{rel_path}:{frame_info.lineno} in {frame_info.function}")
77
+
78
+ if len(frames) >= max_frames:
79
+ break
80
+
81
+ if not frames:
82
+ return "<unknown>" if max_frames == 1 else ["<unknown>"]
83
+
84
+ return frames[0] if max_frames == 1 else frames
85
+
86
+
87
+ def _relative_path(filepath: str) -> str:
88
+ """Return path relative to cwd, or absolute if outside cwd."""
89
+ try:
90
+ return os.path.relpath(filepath)
91
+ except ValueError:
92
+ return filepath
93
+
94
+
95
+ class SQLCallerFilter(logging.Filter):
96
+ """logging.Filter that rewrites LogRecord caller fields to user-code location.
97
+
98
+ Inspects the call stack at filter time, skips library frames (site-packages
99
+ and sqlobjects.*/sqlalchemy.* modules for editable installs), and overwrites
100
+ record.filename / record.funcName / record.lineno / record.pathname so that
101
+ any handler (including loguru interception) displays the real user-code
102
+ call site.
103
+
104
+ Also exposes record.caller (str or list[str]) for use in custom Formatters.
105
+
106
+ Args:
107
+ max_frames: Number of user-code frames to capture (default 1).
108
+ When 1, record.caller is a str and record location fields point
109
+ to that single frame.
110
+ When > 1, record.caller is a list[str] and record location fields
111
+ are set from the first (most recent) frame.
112
+ extra_skip_packages: Additional module name prefixes to skip
113
+ (matched against frame's __name__).
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ max_frames: int = 1,
119
+ extra_skip_packages: list[str] | None = None,
120
+ ) -> None:
121
+ super().__init__()
122
+ self.max_frames = max_frames
123
+ self.extra_skip_packages: list[str] | None = list(extra_skip_packages) if extra_skip_packages else None
124
+
125
+ def filter(self, record: logging.LogRecord) -> bool:
126
+ caller = get_caller_frame(
127
+ extra_skip_packages=self.extra_skip_packages,
128
+ max_frames=self.max_frames,
129
+ )
130
+ record.caller = caller
131
+
132
+ # Overwrite standard location fields from the first user frame
133
+ first = caller if isinstance(caller, str) else caller[0]
134
+ self._overwrite_record_location(record, first)
135
+
136
+ return True
137
+
138
+ @staticmethod
139
+ def _overwrite_record_location(record: logging.LogRecord, frame_str: str) -> None:
140
+ """Parse 'path/to/file.py:lineno in funcname' and overwrite record fields.
141
+
142
+ Frame string format: "relative/path/to/file.py:42 in func_name"
143
+ Uses rsplit to handle edge cases where function name could have spaces.
144
+ """
145
+ try:
146
+ # Split off the function name part (rightmost " in ")
147
+ path_part, func_part = frame_str.rsplit(" in ", 1)
148
+ # Split off the line number (rightmost ":")
149
+ filepath, lineno_str = path_part.rsplit(":", 1)
150
+ record.pathname = os.path.abspath(filepath)
151
+ record.filename = os.path.basename(filepath)
152
+ record.module = os.path.splitext(record.filename)[0]
153
+ record.funcName = func_part.strip()
154
+ record.lineno = int(lineno_str)
155
+ except (ValueError, AttributeError):
156
+ pass # Keep original fields if parsing fails
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlobjects
3
- Version: 1.6.0
3
+ Version: 1.8.0
4
4
  Summary: Django-style async ORM library based on SQLAlchemy with chainable queries, Q objects, and relationship loading
5
5
  Author-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
6
6
  Maintainer-email: XtraVisions <gitadmin@xtravisions.com>, Chen Hao <chenhao@xtravisions.com>
@@ -20,6 +20,7 @@ sqlobjects/model.py
20
20
  sqlobjects/queryset.py
21
21
  sqlobjects/session.py
22
22
  sqlobjects/signals.py
23
+ sqlobjects/sql_logging.py
23
24
  sqlobjects/validators.py
24
25
  sqlobjects.egg-info/PKG-INFO
25
26
  sqlobjects.egg-info/SOURCES.txt
File without changes
File without changes
File without changes