1from __future__ import annotations
2
3from .types import Source, TableOwner
4
5
6def build_table_owners() -> dict[str, TableOwner]:
7 """Map table names to their owning package, source, and model class."""
8 import inspect
9
10 from plain.packages import packages_registry
11 from plain.postgres import models_registry
12
13 def _source_file(cls: type) -> str:
14 try:
15 path = inspect.getsourcefile(cls)
16 except (TypeError, OSError):
17 return ""
18 return path or ""
19
20 owners: dict[str, TableOwner] = {}
21 for package_config in packages_registry.get_package_configs():
22 source = "app" if package_config.name.startswith("app.") else "package"
23 for model in models_registry.get_models(
24 package_label=package_config.package_label
25 ):
26 owners[model.model_options.db_table] = TableOwner(
27 package_label=package_config.package_label,
28 source=source,
29 model_class=model.__name__,
30 model_file=_source_file(model),
31 )
32 for field in model._model_meta.local_many_to_many:
33 m2m_table = field.m2m_db_table()
34 if m2m_table in owners:
35 # Explicit "through" model already registered with its class.
36 continue
37 owners[m2m_table] = TableOwner(
38 package_label=package_config.package_label,
39 source=source,
40 model_class="", # auto-generated join table, no class
41 model_file="",
42 )
43 return owners
44
45
46def _table_info(
47 table_name: str, table_owners: dict[str, TableOwner]
48) -> tuple[Source, str, str, str]:
49 """Return (source, package, model_class, model_file) for a table name.
50
51 model_class and model_file are populated only for app-owned tables so
52 findings can suggest exact model edits. Package tables have a model class
53 but the user can't edit it from their app.
54 """
55 owner = table_owners.get(table_name)
56 if not owner:
57 return "", "", "", ""
58 if owner["source"] == "app":
59 return (
60 owner["source"],
61 owner["package_label"],
62 owner["model_class"],
63 owner["model_file"],
64 )
65 return owner["source"], owner["package_label"], "", ""