TypeDAL 4.4.4__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.4 → 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.4 → typedal-4.4.5}/CHANGELOG.md +7 -0
  5. {typedal-4.4.4 → typedal-4.4.5}/PKG-INFO +1 -1
  6. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/__about__.py +1 -1
  7. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/caching.py +1 -1
  8. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/config.py +1 -3
  9. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/core.py +7 -7
  10. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/define.py +3 -3
  11. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/mixins.py +1 -1
  12. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/query_builder.py +1 -1
  13. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/relationships.py +28 -5
  14. {typedal-4.4.4 → typedal-4.4.5}/tests/test_relationships.py +4 -3
  15. typedal-4.4.4/.crush/crush.db-wal +0 -0
  16. typedal-4.4.4/.crush/logs/crush.log +0 -34
  17. {typedal-4.4.4 → typedal-4.4.5}/.crush/.gitignore +0 -0
  18. {typedal-4.4.4 → typedal-4.4.5}/.crush/init +0 -0
  19. {typedal-4.4.4 → typedal-4.4.5}/.github/workflows/su6.yml +0 -0
  20. {typedal-4.4.4 → typedal-4.4.5}/.gitignore +0 -0
  21. {typedal-4.4.4 → typedal-4.4.5}/.readthedocs.yml +0 -0
  22. {typedal-4.4.4 → typedal-4.4.5}/README.md +0 -0
  23. {typedal-4.4.4 → typedal-4.4.5}/coverage.svg +0 -0
  24. {typedal-4.4.4 → typedal-4.4.5}/docs/1_getting_started.md +0 -0
  25. {typedal-4.4.4 → typedal-4.4.5}/docs/2_defining_tables.md +0 -0
  26. {typedal-4.4.4 → typedal-4.4.5}/docs/3_building_queries.md +0 -0
  27. {typedal-4.4.4 → typedal-4.4.5}/docs/4_relationships.md +0 -0
  28. {typedal-4.4.4 → typedal-4.4.5}/docs/5_py4web.md +0 -0
  29. {typedal-4.4.4 → typedal-4.4.5}/docs/6_migrations.md +0 -0
  30. {typedal-4.4.4 → typedal-4.4.5}/docs/7_configuration.md +0 -0
  31. {typedal-4.4.4 → typedal-4.4.5}/docs/8_mixins.md +0 -0
  32. {typedal-4.4.4 → typedal-4.4.5}/docs/9_memoization.md +0 -0
  33. {typedal-4.4.4 → typedal-4.4.5}/docs/css/code_blocks.css +0 -0
  34. {typedal-4.4.4 → typedal-4.4.5}/docs/index.md +0 -0
  35. {typedal-4.4.4 → typedal-4.4.5}/docs/requirements.txt +0 -0
  36. {typedal-4.4.4 → typedal-4.4.5}/example_new.py +0 -0
  37. {typedal-4.4.4 → typedal-4.4.5}/example_old.py +0 -0
  38. {typedal-4.4.4 → typedal-4.4.5}/mkdocs.yml +0 -0
  39. {typedal-4.4.4 → typedal-4.4.5}/pyproject.toml +0 -0
  40. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/__init__.py +0 -0
  41. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/cli.py +0 -0
  42. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/constants.py +0 -0
  43. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/fields.py +0 -0
  44. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/for_py4web.py +0 -0
  45. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/for_web2py.py +0 -0
  46. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/helpers.py +0 -0
  47. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/py.typed +0 -0
  48. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/rows.py +0 -0
  49. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/serializers/as_json.py +0 -0
  50. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/tables.py +0 -0
  51. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/types.py +0 -0
  52. {typedal-4.4.4 → typedal-4.4.5}/src/typedal/web2py_py4web_shared.py +0 -0
  53. {typedal-4.4.4 → typedal-4.4.5}/tasks.py +0 -0
  54. {typedal-4.4.4 → typedal-4.4.5}/tests/__init__.py +0 -0
  55. {typedal-4.4.4 → typedal-4.4.5}/tests/configs/simple.toml +0 -0
  56. {typedal-4.4.4 → typedal-4.4.5}/tests/configs/valid.env +0 -0
  57. {typedal-4.4.4 → typedal-4.4.5}/tests/configs/valid.toml +0 -0
  58. {typedal-4.4.4 → typedal-4.4.5}/tests/py314_tests.py +0 -0
  59. {typedal-4.4.4 → typedal-4.4.5}/tests/test_cli.py +0 -0
  60. {typedal-4.4.4 → typedal-4.4.5}/tests/test_config.py +0 -0
  61. {typedal-4.4.4 → typedal-4.4.5}/tests/test_docs_examples.py +0 -0
  62. {typedal-4.4.4 → typedal-4.4.5}/tests/test_helpers.py +0 -0
  63. {typedal-4.4.4 → typedal-4.4.5}/tests/test_json.py +0 -0
  64. {typedal-4.4.4 → typedal-4.4.5}/tests/test_main.py +0 -0
  65. {typedal-4.4.4 → typedal-4.4.5}/tests/test_mixins.py +0 -0
  66. {typedal-4.4.4 → typedal-4.4.5}/tests/test_mypy.py +0 -0
  67. {typedal-4.4.4 → typedal-4.4.5}/tests/test_orm.py +0 -0
  68. {typedal-4.4.4 → typedal-4.4.5}/tests/test_py4web.py +0 -0
  69. {typedal-4.4.4 → typedal-4.4.5}/tests/test_query_builder.py +0 -0
  70. {typedal-4.4.4 → typedal-4.4.5}/tests/test_row.py +0 -0
  71. {typedal-4.4.4 → typedal-4.4.5}/tests/test_stats.py +0 -0
  72. {typedal-4.4.4 → typedal-4.4.5}/tests/test_table.py +0 -0
  73. {typedal-4.4.4 → typedal-4.4.5}/tests/test_web2py.py +0 -0
  74. {typedal-4.4.4 → typedal-4.4.5}/tests/test_xx_others.py +0 -0
  75. {typedal-4.4.4 → 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,13 @@
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
+
5
12
  ## v4.4.4 (2026-02-25)
6
13
 
7
14
  ### Fix
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypeDAL
3
- Version: 4.4.4
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.4"
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
@@ -63,6 +64,10 @@ class Relationship(t.Generic[To_Type]):
63
64
  self.condition_and = condition_and
64
65
  self._lazy = lazy
65
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
+
66
71
  if args := t.get_args(_type):
67
72
  self.table = unwrap_type(args[0])
68
73
  self.multiple = True
@@ -70,6 +75,12 @@ class Relationship(t.Generic[To_Type]):
70
75
  self.table = t.cast(type[TypedTable], _type)
71
76
  self.multiple = False
72
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
+
73
84
  if isinstance(self.table, str):
74
85
  self.table = TypeDAL.to_snake(self.table)
75
86
 
@@ -264,6 +275,18 @@ class Relationship(t.Generic[To_Type]):
264
275
  return fallback_value
265
276
 
266
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
+
267
290
  @t.overload
268
291
  def relationship(
269
292
  _type: type[list[To_Type]],
@@ -286,7 +309,7 @@ def relationship(
286
309
 
287
310
  @t.overload
288
311
  def relationship(
289
- _type: t.Type[To_Type] | str,
312
+ _type: t.Type[To_Type] | str | t.Type[Ref[To_Type]],
290
313
  condition: Condition = None,
291
314
  *,
292
315
  join: t.Literal["inner"],
@@ -308,7 +331,7 @@ def relationship(
308
331
 
309
332
  @t.overload
310
333
  def relationship(
311
- _type: t.Type[To_Type] | str,
334
+ _type: t.Type[To_Type] | str | t.Type[Ref[To_Type]],
312
335
  condition: Condition = None,
313
336
  join: JOIN_OPTIONS = None,
314
337
  on: OnQuery = None,
@@ -327,7 +350,7 @@ def relationship(
327
350
 
328
351
 
329
352
  def relationship(
330
- _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]],
331
354
  condition: Condition = None,
332
355
  join: JOIN_OPTIONS = None,
333
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,8 +16,8 @@ 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
22
  db = TypeDAL("sqlite:memory", lazy_policy="warn")
23
23
 
@@ -970,7 +970,8 @@ class Office(TypedTable):
970
970
  city_id: City
971
971
  company: "Company"
972
972
 
973
- 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)
974
975
 
975
976
 
976
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