TypeDAL 4.4.3__tar.gz → 4.4.5__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 (75) hide show
  1. {typedal-4.4.3 → typedal-4.4.5}/.crush/crush.db-shm +0 -0
  2. typedal-4.4.5/.crush/crush.db-wal +0 -0
  3. typedal-4.4.5/.crush/logs/crush.log +37 -0
  4. {typedal-4.4.3 → typedal-4.4.5}/CHANGELOG.md +13 -0
  5. {typedal-4.4.3 → typedal-4.4.5}/PKG-INFO +1 -1
  6. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/__about__.py +1 -1
  7. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/caching.py +1 -1
  8. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/config.py +1 -3
  9. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/core.py +7 -7
  10. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/define.py +3 -3
  11. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/mixins.py +1 -1
  12. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/query_builder.py +1 -1
  13. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/relationships.py +36 -6
  14. {typedal-4.4.3 → typedal-4.4.5}/tests/test_relationships.py +8 -4
  15. typedal-4.4.3/.crush/crush.db-wal +0 -0
  16. typedal-4.4.3/.crush/logs/crush.log +0 -34
  17. {typedal-4.4.3 → typedal-4.4.5}/.crush/.gitignore +0 -0
  18. {typedal-4.4.3 → typedal-4.4.5}/.crush/init +0 -0
  19. {typedal-4.4.3 → typedal-4.4.5}/.github/workflows/su6.yml +0 -0
  20. {typedal-4.4.3 → typedal-4.4.5}/.gitignore +0 -0
  21. {typedal-4.4.3 → typedal-4.4.5}/.readthedocs.yml +0 -0
  22. {typedal-4.4.3 → typedal-4.4.5}/README.md +0 -0
  23. {typedal-4.4.3 → typedal-4.4.5}/coverage.svg +0 -0
  24. {typedal-4.4.3 → typedal-4.4.5}/docs/1_getting_started.md +0 -0
  25. {typedal-4.4.3 → typedal-4.4.5}/docs/2_defining_tables.md +0 -0
  26. {typedal-4.4.3 → typedal-4.4.5}/docs/3_building_queries.md +0 -0
  27. {typedal-4.4.3 → typedal-4.4.5}/docs/4_relationships.md +0 -0
  28. {typedal-4.4.3 → typedal-4.4.5}/docs/5_py4web.md +0 -0
  29. {typedal-4.4.3 → typedal-4.4.5}/docs/6_migrations.md +0 -0
  30. {typedal-4.4.3 → typedal-4.4.5}/docs/7_configuration.md +0 -0
  31. {typedal-4.4.3 → typedal-4.4.5}/docs/8_mixins.md +0 -0
  32. {typedal-4.4.3 → typedal-4.4.5}/docs/9_memoization.md +0 -0
  33. {typedal-4.4.3 → typedal-4.4.5}/docs/css/code_blocks.css +0 -0
  34. {typedal-4.4.3 → typedal-4.4.5}/docs/index.md +0 -0
  35. {typedal-4.4.3 → typedal-4.4.5}/docs/requirements.txt +0 -0
  36. {typedal-4.4.3 → typedal-4.4.5}/example_new.py +0 -0
  37. {typedal-4.4.3 → typedal-4.4.5}/example_old.py +0 -0
  38. {typedal-4.4.3 → typedal-4.4.5}/mkdocs.yml +0 -0
  39. {typedal-4.4.3 → typedal-4.4.5}/pyproject.toml +0 -0
  40. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/__init__.py +0 -0
  41. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/cli.py +0 -0
  42. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/constants.py +0 -0
  43. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/fields.py +0 -0
  44. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/for_py4web.py +0 -0
  45. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/for_web2py.py +0 -0
  46. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/helpers.py +0 -0
  47. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/py.typed +0 -0
  48. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/rows.py +0 -0
  49. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/serializers/as_json.py +0 -0
  50. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/tables.py +0 -0
  51. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/types.py +0 -0
  52. {typedal-4.4.3 → typedal-4.4.5}/src/typedal/web2py_py4web_shared.py +0 -0
  53. {typedal-4.4.3 → typedal-4.4.5}/tasks.py +0 -0
  54. {typedal-4.4.3 → typedal-4.4.5}/tests/__init__.py +0 -0
  55. {typedal-4.4.3 → typedal-4.4.5}/tests/configs/simple.toml +0 -0
  56. {typedal-4.4.3 → typedal-4.4.5}/tests/configs/valid.env +0 -0
  57. {typedal-4.4.3 → typedal-4.4.5}/tests/configs/valid.toml +0 -0
  58. {typedal-4.4.3 → typedal-4.4.5}/tests/py314_tests.py +0 -0
  59. {typedal-4.4.3 → typedal-4.4.5}/tests/test_cli.py +0 -0
  60. {typedal-4.4.3 → typedal-4.4.5}/tests/test_config.py +0 -0
  61. {typedal-4.4.3 → typedal-4.4.5}/tests/test_docs_examples.py +0 -0
  62. {typedal-4.4.3 → typedal-4.4.5}/tests/test_helpers.py +0 -0
  63. {typedal-4.4.3 → typedal-4.4.5}/tests/test_json.py +0 -0
  64. {typedal-4.4.3 → typedal-4.4.5}/tests/test_main.py +0 -0
  65. {typedal-4.4.3 → typedal-4.4.5}/tests/test_mixins.py +0 -0
  66. {typedal-4.4.3 → typedal-4.4.5}/tests/test_mypy.py +0 -0
  67. {typedal-4.4.3 → typedal-4.4.5}/tests/test_orm.py +0 -0
  68. {typedal-4.4.3 → typedal-4.4.5}/tests/test_py4web.py +0 -0
  69. {typedal-4.4.3 → typedal-4.4.5}/tests/test_query_builder.py +0 -0
  70. {typedal-4.4.3 → typedal-4.4.5}/tests/test_row.py +0 -0
  71. {typedal-4.4.3 → typedal-4.4.5}/tests/test_stats.py +0 -0
  72. {typedal-4.4.3 → typedal-4.4.5}/tests/test_table.py +0 -0
  73. {typedal-4.4.3 → typedal-4.4.5}/tests/test_web2py.py +0 -0
  74. {typedal-4.4.3 → typedal-4.4.5}/tests/test_xx_others.py +0 -0
  75. {typedal-4.4.3 → typedal-4.4.5}/tests/timings.py +0 -0
Binary file
@@ -0,0 +1,37 @@
1
+ {"time":"2026-01-20T14:19:21.359487106+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
2
+ {"time":"2026-01-20T14:19:21.518115889+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
3
+ {"time":"2026-01-20T14:19:21.52626179+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.52ms)"}
4
+ {"time":"2026-01-20T14:19:21.526651475+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (330.71µs)"}
5
+ {"time":"2026-01-20T14:19:21.526995425+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (325.29µs)"}
6
+ {"time":"2026-01-20T14:19:21.527303588+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (293.15µs)"}
7
+ {"time":"2026-01-20T14:19:21.52790648+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (495.71µs)"}
8
+ {"time":"2026-01-20T14:19:21.528316924+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (389.38µs)"}
9
+ {"time":"2026-01-20T14:19:21.528324939+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20250812000000"}
10
+ {"time":"2026-01-20T14:19:21.528356959+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
11
+ {"time":"2026-01-20T14:19:21.52843831+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
12
+ {"time":"2026-01-20T14:21:06.413595152+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
13
+ {"time":"2026-01-20T14:21:14.539289381+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
14
+ {"time":"2026-01-20T14:21:15.710688424+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
15
+ {"time":"2026-01-20T14:21:17.08089755+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
16
+ {"time":"2026-01-20T14:21:17.151106375+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
17
+ {"time":"2026-01-20T14:22:05.503761296+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
18
+ {"time":"2026-01-20T14:26:05.030425805+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
19
+ {"time":"2026-01-20T14:36:26.778665189+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
20
+ {"time":"2026-01-20T14:38:51.314473736+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
21
+ {"time":"2026-01-20T15:02:22.602405814+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":403},"msg":"Shutdown took 6.62949ms"}
22
+ {"time":"2026-01-20T15:02:23.719585648+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
23
+ {"time":"2026-01-20T15:02:23.881789126+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
24
+ {"time":"2026-01-20T15:02:23.884044691+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
25
+ {"time":"2026-01-20T15:02:23.884082792+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
26
+ {"time":"2026-01-20T15:02:23.884273487+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
27
+ {"time":"2026-01-20T15:04:13.024457515+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
28
+ {"time":"2026-01-20T15:04:14.232072885+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
29
+ {"time":"2026-01-20T15:11:24.612161916+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":403},"msg":"Shutdown took 9.537141ms"}
30
+ {"time":"2026-01-20T15:11:25.263960575+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
31
+ {"time":"2026-01-20T15:11:25.431990051+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
32
+ {"time":"2026-01-20T15:11:25.434282035+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
33
+ {"time":"2026-01-20T15:11:25.434366662+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
34
+ {"time":"2026-01-20T15:11:25.434579879+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":102},"msg":"Initializing MCP clients"}
35
+ {"time":"2026-01-20T15:24:40.456010578+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
36
+ {"time":"2026-01-20T15:24:51.673808433+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
37
+ {"time":"2026-01-20T15:26:03.868420566+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).Cancel","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":907},"msg":"Request cancellation initiated","session_id":"4c2d1684-8db2-4ffd-83bb-9c08fe850f29"}
@@ -2,6 +2,19 @@
2
2
 
3
3
  <!--next-version-placeholder-->
4
4
 
5
+ ## v4.4.5 (2026-02-27)
6
+
7
+ ### Fix
8
+
9
+ * Add `Ref` type so you can do forward references in types, ([`3240a74`](https://github.com/trialandsuccess/TypeDAL/commit/3240a747a4dd63a887464c0a748c68907d38d7d0))
10
+ * Pass known classes as namespace so `col: "ForwardRef"` works in 3.13 too ([`b655da8`](https://github.com/trialandsuccess/TypeDAL/commit/b655da812d7ea014b95adf73974ca301a6c42ca2))
11
+
12
+ ## v4.4.4 (2026-02-25)
13
+
14
+ ### Fix
15
+
16
+ * **relationship:** Ensure get_db() also works with forward references by looking at other table (owner) as fallback ([`17dd7b4`](https://github.com/trialandsuccess/TypeDAL/commit/17dd7b4273819f2edf6d427264ee1a81622ef597))
17
+
5
18
  ## v4.4.3 (2026-02-11)
6
19
 
7
20
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.4.3
3
+ Version: 4.4.5
4
4
  Summary: Typing support for PyDAL
5
5
  Project-URL: Documentation, https://typedal.readthedocs.io/
6
6
  Project-URL: Issues, https://github.com/trialandsuccess/TypeDAL/issues
@@ -5,4 +5,4 @@ This file contains the Version info for this package.
5
5
  # SPDX-FileCopyrightText: 2023-present Robin van der Noord <robinvandernoord@gmail.com>
6
6
  #
7
7
  # SPDX-License-Identifier: MIT
8
- __version__ = "4.4.3"
8
+ __version__ = "4.4.5"
@@ -577,7 +577,7 @@ def memoize(
577
577
  return cached, "cached"
578
578
  # Cache miss - compute result
579
579
 
580
- def track_execute(_qb: "QueryBuilder[t.Any]", raw: Rows):
580
+ def track_execute(_qb: "QueryBuilder[t.Any]", raw: Rows) -> None:
581
581
  # find dependant table+id combinations, includes relationships:
582
582
  deps.update(_determine_dependencies_auto(raw))
583
583
 
@@ -9,13 +9,11 @@ from pathlib import Path
9
9
 
10
10
  import tomli
11
11
  from configuraptor import TypedConfig, alias
12
- from configuraptor.helpers import find_pyproject_toml
12
+ from configuraptor.helpers import expand_env_vars_into_toml_values, find_pyproject_toml
13
13
  from dotenv import dotenv_values, find_dotenv
14
14
 
15
15
  from .types import AnyDict
16
16
 
17
- from configuraptor.helpers import expand_env_vars_into_toml_values
18
-
19
17
  if t.TYPE_CHECKING:
20
18
  from edwh_migrate import Config as MigrateConfig
21
19
  from pydal2sql.typer_support import Config as P2SConfig
@@ -106,27 +106,27 @@ def evaluate_forward_reference(
106
106
  return evaluate_forward_reference_314(fw_ref, namespace=namespace or {})
107
107
 
108
108
 
109
- def resolve_annotation_313(ftype: str) -> type: # pragma: no cover
109
+ def resolve_annotation_313(ftype: str, namespace: dict[str, type] | None = None) -> type: # pragma: no cover
110
110
  """
111
111
  Resolve an annotation that's in string representation.
112
112
 
113
113
  Variant for Python 3.13
114
114
  """
115
115
  fw_ref: ForwardRef = t.get_args(t.Type[ftype])[0]
116
- return evaluate_forward_reference(fw_ref)
116
+ return evaluate_forward_reference(fw_ref, namespace=namespace)
117
117
 
118
118
 
119
- def resolve_annotation_314(ftype: str) -> type: # pragma: no cover
119
+ def resolve_annotation_314(ftype: str, namespace: dict[str, type] | None = None) -> type: # pragma: no cover
120
120
  """
121
121
  Resolve an annotation that's in string representation.
122
122
 
123
123
  Variant for Python 3.14 + using annotationlib
124
124
  """
125
125
  fw_ref = ForwardRef(ftype)
126
- return evaluate_forward_reference(fw_ref)
126
+ return evaluate_forward_reference(fw_ref, namespace=namespace)
127
127
 
128
128
 
129
- def resolve_annotation(ftype: str) -> type: # pragma: no cover
129
+ def resolve_annotation(ftype: str, namespace: dict[str, type] | None = None) -> type: # pragma: no cover
130
130
  """
131
131
  Resolve an annotation that's in string representation.
132
132
 
@@ -135,9 +135,9 @@ def resolve_annotation(ftype: str) -> type: # pragma: no cover
135
135
  if sys.version_info.major != 3:
136
136
  raise EnvironmentError("Only python 3 is supported.")
137
137
  elif sys.version_info.minor <= 13:
138
- return resolve_annotation_313(ftype)
138
+ return resolve_annotation_313(ftype, namespace=namespace)
139
139
  else:
140
- return resolve_annotation_314(ftype)
140
+ return resolve_annotation_314(ftype, namespace=namespace)
141
141
 
142
142
 
143
143
  class TypeDAL(pydal.DAL):
@@ -133,13 +133,13 @@ class TableDefinitionBuilder:
133
133
  """Convert Python type annotation to pydal field type string."""
134
134
  ftype = t.cast(type, ftype_annotation) # cast from Type to type to make mypy happy)
135
135
 
136
+ known_classes = {table.__name__: table for table in self.class_map.values()}
137
+
136
138
  if isinstance(ftype, str):
137
139
  # extract type from string
138
- ftype = resolve_annotation(ftype)
140
+ ftype = resolve_annotation(ftype, namespace=known_classes)
139
141
 
140
142
  if isinstance(ftype, ForwardRef):
141
- known_classes = {table.__name__: table for table in self.class_map.values()}
142
-
143
143
  ftype = evaluate_forward_reference(ftype, namespace=known_classes)
144
144
 
145
145
  if mapping := BASIC_MAPPINGS.get(ftype):
@@ -116,7 +116,7 @@ class HAS_UNIQUE_SLUG(IS_NOT_IN_DB):
116
116
  if not value.strip():
117
117
  raise ValidationError(self.translator(self.error_message))
118
118
 
119
- (tablename, fieldname) = str(self.field).split(".")
119
+ tablename, fieldname = str(self.field).split(".")
120
120
  table = self.dbset.db[tablename]
121
121
  field = table[fieldname]
122
122
  query = field == value
@@ -555,7 +555,7 @@ class QueryBuilder(t.Generic[T_MetaInstance]):
555
555
  for fn_before in db._before_execute:
556
556
  fn_before(self)
557
557
 
558
- rows = db(query).select(*select_args, **select_kwargs)
558
+ rows: Rows = db(query).select(*select_args, **select_kwargs)
559
559
 
560
560
  for fn_after in db._after_execute:
561
561
  fn_after(self, rows)
@@ -5,17 +5,18 @@ Contains base functionality related to Relationships.
5
5
  import inspect
6
6
  import typing as t
7
7
  import warnings
8
+ from typing import ForwardRef
8
9
 
9
10
  import pydal.objects
10
11
 
11
12
  from .config import LazyPolicy
12
13
  from .constants import JOIN_OPTIONS
13
- from .core import TypeDAL
14
+ from .core import TypeDAL, evaluate_forward_reference
14
15
  from .fields import TypedField
15
16
  from .helpers import extract_type_optional, looks_like, unwrap_type
16
17
  from .types import Condition, OnQuery, T_Field
17
18
 
18
- To_Type = t.TypeVar("To_Type")
19
+ To_Type = t.TypeVar("To_Type", bound="TypedTable")
19
20
 
20
21
 
21
22
  # default lazy policy is defined at the TypeDAL() instance settings level
@@ -37,6 +38,7 @@ class Relationship(t.Generic[To_Type]):
37
38
  nested: dict[str, t.Self]
38
39
  explicit: bool
39
40
  name: str | None = None # set by __set_name__
41
+ _owner: t.Type["TypedTable"] | None = None
40
42
 
41
43
  def __init__(
42
44
  self,
@@ -62,6 +64,10 @@ class Relationship(t.Generic[To_Type]):
62
64
  self.condition_and = condition_and
63
65
  self._lazy = lazy
64
66
 
67
+ if t.get_origin(_type) == Ref:
68
+ # unwrap Ref["City"] to ForwardRef("City") to be evaluated/stringified later on:
69
+ _type = t.get_args(_type)[0]
70
+
65
71
  if args := t.get_args(_type):
66
72
  self.table = unwrap_type(args[0])
67
73
  self.multiple = True
@@ -69,6 +75,12 @@ class Relationship(t.Generic[To_Type]):
69
75
  self.table = t.cast(type[TypedTable], _type)
70
76
  self.multiple = False
71
77
 
78
+ if isinstance(self.table, ForwardRef):
79
+ try:
80
+ self.table = evaluate_forward_reference(self.table)
81
+ except Exception:
82
+ self.table = self.table.__forward_arg__
83
+
72
84
  if isinstance(self.table, str):
73
85
  self.table = TypeDAL.to_snake(self.table)
74
86
 
@@ -120,6 +132,7 @@ class Relationship(t.Generic[To_Type]):
120
132
 
121
133
  def __set_name__(self, owner: t.Type["TypedTable"], name: str) -> None:
122
134
  """Called automatically when assigned to a class attribute."""
135
+ self._owner = owner
123
136
  self.name = name
124
137
 
125
138
  def get_table(self, db: "TypeDAL") -> t.Type["TypedTable"]:
@@ -145,7 +158,12 @@ class Relationship(t.Generic[To_Type]):
145
158
  Returns:
146
159
  TypeDAL | None: The database instance if it exists, or None otherwise.
147
160
  """
148
- return getattr(self.table, "_db", None)
161
+ target = self.table
162
+
163
+ if isinstance(target, str) and (owner := self._owner):
164
+ target = owner
165
+
166
+ return getattr(target, "_db", None)
149
167
 
150
168
  @property
151
169
  def lazy(self) -> LazyPolicy:
@@ -257,6 +275,18 @@ class Relationship(t.Generic[To_Type]):
257
275
  return fallback_value
258
276
 
259
277
 
278
+ class Ref(t.Generic[To_Type]):
279
+ """
280
+ Type-level forward reference wrapper.
281
+
282
+ Allows writing:
283
+
284
+ relationship(Ref["User"])
285
+
286
+ so that type checkers resolve the inner type correctly.
287
+ """
288
+
289
+
260
290
  @t.overload
261
291
  def relationship(
262
292
  _type: type[list[To_Type]],
@@ -279,7 +309,7 @@ def relationship(
279
309
 
280
310
  @t.overload
281
311
  def relationship(
282
- _type: t.Type[To_Type] | str,
312
+ _type: t.Type[To_Type] | str | t.Type[Ref[To_Type]],
283
313
  condition: Condition = None,
284
314
  *,
285
315
  join: t.Literal["inner"],
@@ -301,7 +331,7 @@ def relationship(
301
331
 
302
332
  @t.overload
303
333
  def relationship(
304
- _type: t.Type[To_Type] | str,
334
+ _type: t.Type[To_Type] | str | t.Type[Ref[To_Type]],
305
335
  condition: Condition = None,
306
336
  join: JOIN_OPTIONS = None,
307
337
  on: OnQuery = None,
@@ -320,7 +350,7 @@ def relationship(
320
350
 
321
351
 
322
352
  def relationship(
323
- _type: type[list[To_Type]] | t.Type[To_Type] | str,
353
+ _type: type[list[To_Type]] | t.Type[To_Type] | str | t.Type[Ref[To_Type]],
324
354
  condition: Condition = None,
325
355
  join: JOIN_OPTIONS = None,
326
356
  on: OnQuery = None,
@@ -8,7 +8,7 @@ from uuid import uuid4
8
8
 
9
9
  import pytest
10
10
 
11
- from src.typedal import Relationship, TypeDAL, TypedField, TypedTable, relationship
11
+ from src.typedal import Relationship, TypeDAL, TypedField, TypedRows, TypedTable, relationship
12
12
  from src.typedal.caching import (
13
13
  _TypedalCache,
14
14
  _TypedalCacheDependency,
@@ -16,10 +16,10 @@ from src.typedal.caching import (
16
16
  clear_expired,
17
17
  remove_cache,
18
18
  )
19
+ from src.typedal.relationships import Ref
19
20
  from src.typedal.serializers import as_json
20
- from typedal import TypedRows
21
21
 
22
- db = TypeDAL("sqlite:memory")
22
+ db = TypeDAL("sqlite:memory", lazy_policy="warn")
23
23
 
24
24
 
25
25
  class TaggableMixin:
@@ -336,6 +336,7 @@ def test_reprs():
336
336
  empty = Relationship("article")
337
337
 
338
338
  assert empty.get_table(db) == Article
339
+ assert empty.get_db() is None
339
340
 
340
341
  db.define_table("new")
341
342
  empty = Relationship("new")
@@ -345,8 +346,10 @@ def test_reprs():
345
346
 
346
347
  empty = Relationship(db.new)
347
348
  assert empty.get_table(db) == db.new
349
+ assert empty.get_db() == db
348
350
 
349
351
  assert empty.get_table_name() == "new"
352
+ assert User.bestie.get_db() == db
350
353
 
351
354
  relation = Article.join("author").relationships["author"]
352
355
 
@@ -967,7 +970,8 @@ class Office(TypedTable):
967
970
  city_id: City
968
971
  company: "Company"
969
972
 
970
- city_alternative = relationship(City, lambda office, city: office.city_id == city.id)
973
+ city = relationship(City, lambda office, city: office.city_id == city.id)
974
+ city_alternative = relationship(Ref["City"], lambda office, city: office.city_id == city.id)
971
975
 
972
976
 
973
977
  class Company(TypedTable):
Binary file
@@ -1,34 +0,0 @@
1
- {"time":"2026-01-26T17:01:48.91963488+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
2
- {"time":"2026-01-26T17:01:49.11634171+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
3
- {"time":"2026-01-26T17:01:49.116589785+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":259},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
4
- {"time":"2026-01-26T17:01:49.128053484+01:00","level":"INFO","msg":"OK 20250424200609_initial.sql (1.26ms)"}
5
- {"time":"2026-01-26T17:01:49.128338994+01:00","level":"INFO","msg":"OK 20250515105448_add_summary_message_id.sql (263.8µs)"}
6
- {"time":"2026-01-26T17:01:49.128678839+01:00","level":"INFO","msg":"OK 20250624000000_add_created_at_indexes.sql (325.2µs)"}
7
- {"time":"2026-01-26T17:01:49.128984204+01:00","level":"INFO","msg":"OK 20250627000000_add_provider_to_messages.sql (291.73µs)"}
8
- {"time":"2026-01-26T17:01:49.12929983+01:00","level":"INFO","msg":"OK 20250810000000_add_is_summary_message.sql (265.62µs)"}
9
- {"time":"2026-01-26T17:01:49.129580779+01:00","level":"INFO","msg":"OK 20250812000000_add_todos_to_sessions.sql (268.09µs)"}
10
- {"time":"2026-01-26T17:01:49.129585964+01:00","level":"INFO","msg":"goose: successfully migrated database to version: 20250812000000"}
11
- {"time":"2026-01-26T17:01:49.129633405+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
12
- {"time":"2026-01-26T17:01:49.129745943+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":110},"msg":"Initializing MCP clients"}
13
- {"time":"2026-01-26T17:01:49.73470854+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).createAndStartLSPClient","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":76},"msg":"LSP client initialized","name":"python"}
14
- {"time":"2026-01-26T17:02:52.962674187+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
15
- {"time":"2026-01-26T17:02:57.3247847+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
16
- {"time":"2026-01-26T17:11:33.432259554+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).Shutdown.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":507},"msg":"Shutdown took 6.286852ms"}
17
- {"time":"2026-01-26T17:11:33.982816329+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
18
- {"time":"2026-01-26T17:11:34.178260248+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
19
- {"time":"2026-01-26T17:11:34.178494988+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":259},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
20
- {"time":"2026-01-26T17:11:34.18216688+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
21
- {"time":"2026-01-26T17:11:34.182194111+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
22
- {"time":"2026-01-26T17:11:34.182335616+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":110},"msg":"Initializing MCP clients"}
23
- {"time":"2026-01-26T17:11:34.786142525+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).createAndStartLSPClient","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":76},"msg":"LSP client initialized","name":"python"}
24
- {"time":"2026-01-26T17:11:43.042930337+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
25
- {"time":"2026-01-26T17:11:44.911909276+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
26
- {"time":"2026-01-26T18:10:19.727802782+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":55},"msg":"Fetching providers from Catwalk"}
27
- {"time":"2026-01-26T18:10:19.926137244+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/config.(*catwalkSync).Get.func1","file":"github.com/charmbracelet/crush/internal/config/catwalk.go","line":63},"msg":"Catwalk providers not modified"}
28
- {"time":"2026-01-26T18:10:19.926245207+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/config.(*Config).configureProviders","file":"github.com/charmbracelet/crush/internal/config/load.go","line":259},"msg":"Skipping provider due to missing API key","provider":"anthropic"}
29
- {"time":"2026-01-26T18:10:19.927808467+01:00","level":"INFO","msg":"goose: no migrations to run. current version: 20250812000000"}
30
- {"time":"2026-01-26T18:10:19.927858435+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).initLSPClients","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":21},"msg":"LSP clients initialization started in background"}
31
- {"time":"2026-01-26T18:10:19.928006013+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.New.func1","file":"github.com/charmbracelet/crush/internal/app/app.go","line":110},"msg":"Initializing MCP clients"}
32
- {"time":"2026-01-26T18:10:20.546676356+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/app.(*App).createAndStartLSPClient","file":"github.com/charmbracelet/crush/internal/app/lsp.go","line":76},"msg":"LSP client initialized","name":"python"}
33
- {"time":"2026-01-26T18:11:07.640056766+01:00","level":"INFO","source":{"function":"github.com/charmbracelet/crush/internal/agent.(*sessionAgent).generateTitle","file":"github.com/charmbracelet/crush/internal/agent/agent.go","line":788},"msg":"generated title with small model"}
34
- {"time":"2026-01-26T18:11:11.201161834+01:00","level":"WARN","source":{"function":"github.com/charmbracelet/crush/internal/agent/tools.init.func1","file":"github.com/charmbracelet/crush/internal/agent/tools/rg.go","line":18},"msg":"Ripgrep (rg) not found in $PATH. Some grep features might be limited or slower."}
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes