dpmcore 0.0.1__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.
Files changed (100) hide show
  1. dpmcore/__init__.py +7 -0
  2. dpmcore/cli/__init__.py +1 -0
  3. dpmcore/cli/main.py +111 -0
  4. dpmcore/connection.py +207 -0
  5. dpmcore/django/__init__.py +15 -0
  6. dpmcore/django/admin.py +877 -0
  7. dpmcore/django/apps.py +12 -0
  8. dpmcore/django/models/__init__.py +162 -0
  9. dpmcore/django/models/auxiliary.py +222 -0
  10. dpmcore/django/models/glossary.py +624 -0
  11. dpmcore/django/models/infrastructure.py +845 -0
  12. dpmcore/django/models/operations.py +520 -0
  13. dpmcore/django/models/packaging.py +290 -0
  14. dpmcore/django/models/rendering.py +721 -0
  15. dpmcore/django/models/variables.py +232 -0
  16. dpmcore/django/routers.py +68 -0
  17. dpmcore/django/urls.py +8 -0
  18. dpmcore/dpm_xl/__init__.py +8 -0
  19. dpmcore/dpm_xl/ast/__init__.py +14 -0
  20. dpmcore/dpm_xl/ast/constructor.py +514 -0
  21. dpmcore/dpm_xl/ast/ml_generation.py +651 -0
  22. dpmcore/dpm_xl/ast/module_analyzer.py +83 -0
  23. dpmcore/dpm_xl/ast/module_dependencies.py +218 -0
  24. dpmcore/dpm_xl/ast/nodes.py +891 -0
  25. dpmcore/dpm_xl/ast/operands.py +612 -0
  26. dpmcore/dpm_xl/ast/template.py +105 -0
  27. dpmcore/dpm_xl/ast/visitor.py +13 -0
  28. dpmcore/dpm_xl/ast/where_clause.py +12 -0
  29. dpmcore/dpm_xl/grammar/__init__.py +18 -0
  30. dpmcore/dpm_xl/grammar/dpm_xlLexer.g4 +437 -0
  31. dpmcore/dpm_xl/grammar/dpm_xlParser.g4 +263 -0
  32. dpmcore/dpm_xl/grammar/generated/__init__.py +0 -0
  33. dpmcore/dpm_xl/grammar/generated/dpm_xlLexer.interp +432 -0
  34. dpmcore/dpm_xl/grammar/generated/dpm_xlLexer.py +978 -0
  35. dpmcore/dpm_xl/grammar/generated/dpm_xlLexer.tokens +107 -0
  36. dpmcore/dpm_xl/grammar/generated/dpm_xlParser.interp +251 -0
  37. dpmcore/dpm_xl/grammar/generated/dpm_xlParser.py +5372 -0
  38. dpmcore/dpm_xl/grammar/generated/dpm_xlParser.tokens +107 -0
  39. dpmcore/dpm_xl/grammar/generated/dpm_xlParserListener.py +750 -0
  40. dpmcore/dpm_xl/grammar/generated/dpm_xlParserVisitor.py +423 -0
  41. dpmcore/dpm_xl/grammar/generated/listeners.py +12 -0
  42. dpmcore/dpm_xl/model_queries.py +1944 -0
  43. dpmcore/dpm_xl/operators/__init__.py +19 -0
  44. dpmcore/dpm_xl/operators/aggregate.py +137 -0
  45. dpmcore/dpm_xl/operators/arithmetic.py +101 -0
  46. dpmcore/dpm_xl/operators/base.py +388 -0
  47. dpmcore/dpm_xl/operators/boolean.py +30 -0
  48. dpmcore/dpm_xl/operators/clause.py +200 -0
  49. dpmcore/dpm_xl/operators/comparison.py +69 -0
  50. dpmcore/dpm_xl/operators/conditional.py +422 -0
  51. dpmcore/dpm_xl/operators/string.py +27 -0
  52. dpmcore/dpm_xl/operators/time.py +53 -0
  53. dpmcore/dpm_xl/semantic_analyzer.py +453 -0
  54. dpmcore/dpm_xl/symbols.py +223 -0
  55. dpmcore/dpm_xl/types/__init__.py +13 -0
  56. dpmcore/dpm_xl/types/promotion.py +207 -0
  57. dpmcore/dpm_xl/types/scalar.py +326 -0
  58. dpmcore/dpm_xl/types/time.py +370 -0
  59. dpmcore/dpm_xl/utils/__init__.py +14 -0
  60. dpmcore/dpm_xl/utils/data_handlers.py +99 -0
  61. dpmcore/dpm_xl/utils/filters.py +159 -0
  62. dpmcore/dpm_xl/utils/operands_mapping.py +73 -0
  63. dpmcore/dpm_xl/utils/operator_mapping.py +90 -0
  64. dpmcore/dpm_xl/utils/scopes_calculator.py +503 -0
  65. dpmcore/dpm_xl/utils/serialization.py +813 -0
  66. dpmcore/dpm_xl/utils/tokens.py +180 -0
  67. dpmcore/dpm_xl/warning_collector.py +94 -0
  68. dpmcore/errors.py +545 -0
  69. dpmcore/orm/__init__.py +25 -0
  70. dpmcore/orm/auxiliary.py +213 -0
  71. dpmcore/orm/base.py +136 -0
  72. dpmcore/orm/glossary.py +872 -0
  73. dpmcore/orm/infrastructure.py +951 -0
  74. dpmcore/orm/operations.py +731 -0
  75. dpmcore/orm/packaging.py +379 -0
  76. dpmcore/orm/rendering.py +956 -0
  77. dpmcore/orm/variables.py +360 -0
  78. dpmcore/py.typed +0 -0
  79. dpmcore/server/__init__.py +1 -0
  80. dpmcore/server/app.py +170 -0
  81. dpmcore/server/envelope.py +68 -0
  82. dpmcore/server/params.py +90 -0
  83. dpmcore/server/routers/__init__.py +1 -0
  84. dpmcore/server/routers/structure.py +446 -0
  85. dpmcore/services/__init__.py +33 -0
  86. dpmcore/services/ast_generator.py +165 -0
  87. dpmcore/services/data_dictionary.py +191 -0
  88. dpmcore/services/dpm_xl.py +64 -0
  89. dpmcore/services/explorer.py +121 -0
  90. dpmcore/services/hierarchy.py +138 -0
  91. dpmcore/services/migration.py +290 -0
  92. dpmcore/services/scope_calculator.py +154 -0
  93. dpmcore/services/semantic.py +125 -0
  94. dpmcore/services/structure.py +498 -0
  95. dpmcore/services/syntax.py +96 -0
  96. dpmcore-0.0.1.dist-info/METADATA +394 -0
  97. dpmcore-0.0.1.dist-info/RECORD +100 -0
  98. dpmcore-0.0.1.dist-info/WHEEL +4 -0
  99. dpmcore-0.0.1.dist-info/entry_points.txt +3 -0
  100. dpmcore-0.0.1.dist-info/licenses/LICENSE +674 -0
dpmcore/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Your opinionated Python DPM library."""
2
+
3
+ __version__ = "1.0.0"
4
+
5
+ from dpmcore.connection import DpmConnection, connect
6
+
7
+ __all__ = ["__version__", "connect", "DpmConnection"]
@@ -0,0 +1 @@
1
+ """Command-line interface for dpmcore."""
dpmcore/cli/main.py ADDED
@@ -0,0 +1,111 @@
1
+ """CLI entry point for dpmcore.
2
+
3
+ Usage::
4
+
5
+ dpmcore migrate --source /path/to/file.accdb --database sqlite:///dpm.db
6
+ dpmcore serve --database sqlite:///dpm.db
7
+ dpmcore --version
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import sys
13
+
14
+ import click
15
+
16
+ from dpmcore import __version__
17
+
18
+
19
+ @click.group()
20
+ @click.version_option(version=__version__, prog_name="dpmcore")
21
+ def main() -> None:
22
+ """Dpmcore — Data Point Model toolkit."""
23
+
24
+
25
+ @main.command()
26
+ @click.option(
27
+ "--source",
28
+ required=True,
29
+ type=click.Path(exists=True),
30
+ help="Path to Access .accdb / .mdb file.",
31
+ )
32
+ @click.option(
33
+ "--database",
34
+ required=True,
35
+ help="SQLAlchemy database URL (e.g. sqlite:///dpm.db).",
36
+ )
37
+ def migrate(source: str, database: str) -> None:
38
+ """Migrate an Access database into a SQL database."""
39
+ try:
40
+ from rich.console import Console
41
+ from rich.table import Table
42
+ except ImportError:
43
+ click.echo(
44
+ "Install 'rich' for pretty output: "
45
+ "pip install dpmcore[cli]",
46
+ err=True,
47
+ )
48
+ sys.exit(1)
49
+
50
+ from sqlalchemy import create_engine
51
+
52
+ from dpmcore.services.migration import (
53
+ MigrationError,
54
+ MigrationService,
55
+ )
56
+
57
+ console = Console()
58
+
59
+ engine = create_engine(database)
60
+ service = MigrationService(engine)
61
+
62
+ try:
63
+ result = service.migrate_from_access(source)
64
+ except MigrationError as exc:
65
+ console.print(f"[red]Error:[/red] {exc}")
66
+ sys.exit(1)
67
+
68
+ # Display results.
69
+ table = Table(title="Migration Results")
70
+ table.add_column("Table", style="cyan")
71
+ table.add_column("Rows", justify="right", style="green")
72
+
73
+ for name, rows in result.table_details.items():
74
+ table.add_row(name, str(rows))
75
+
76
+ console.print(table)
77
+ console.print(
78
+ f"\n[bold]Total:[/bold] {result.tables_migrated} tables, "
79
+ f"{result.total_rows} rows "
80
+ f"(backend: {result.backend_used})"
81
+ )
82
+
83
+ for warning in result.warnings:
84
+ console.print(f"[yellow]Warning:[/yellow] {warning}")
85
+
86
+
87
+ @main.command()
88
+ @click.option(
89
+ "--database",
90
+ required=True,
91
+ help="SQLAlchemy database URL.",
92
+ )
93
+ @click.option("--host", default="127.0.0.1", help="Bind host.")
94
+ @click.option("--port", default=8000, type=int, help="Bind port.")
95
+ def serve(database: str, host: str, port: int) -> None:
96
+ """Start the dpmcore REST API server."""
97
+ try:
98
+ import uvicorn # type: ignore[import-untyped]
99
+ except ImportError:
100
+ click.echo(
101
+ "Server dependencies not installed. Run:\n"
102
+ " pip install dpmcore[server]",
103
+ err=True,
104
+ )
105
+ sys.exit(1)
106
+
107
+ # Import here so the server module is only loaded when needed.
108
+ from dpmcore.server.app import create_app # type: ignore[import-not-found]
109
+
110
+ app = create_app(database)
111
+ uvicorn.run(app, host=host, port=port)
dpmcore/connection.py ADDED
@@ -0,0 +1,207 @@
1
+ """Connection management for standalone library usage (Mode 1).
2
+
3
+ Usage::
4
+
5
+ from dpmcore import connect
6
+
7
+ db = connect("sqlite:///path/to/dpm.db")
8
+ result = db.services.dpm_xl.validate_syntax("v1 = v2")
9
+ db.close()
10
+
11
+ Or as a context manager::
12
+
13
+ with connect("sqlite:///path/to/dpm.db") as db:
14
+ result = db.services.dpm_xl.validate_syntax("v1 = v2")
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from typing import Any, Dict, Optional
20
+
21
+ from sqlalchemy import Engine, create_engine
22
+ from sqlalchemy.orm import Session, sessionmaker
23
+
24
+ # Ensure all ORM modules are imported so relationships resolve.
25
+ import dpmcore.orm # noqa: F401
26
+
27
+
28
+ class _ServiceAccessor:
29
+ """Lazy container that instantiates services on first access."""
30
+
31
+ def __init__(self, session: Session, engine: Engine) -> None:
32
+ self._session = session
33
+ self._engine = engine
34
+ self._cache: Dict[str, Any] = {}
35
+
36
+ # ------------------------------------------------------------------ #
37
+ # DPM-XL services
38
+ # ------------------------------------------------------------------ #
39
+
40
+ @property
41
+ def dpm_xl(self):
42
+ from dpmcore.services.dpm_xl import DpmXlService
43
+
44
+ if "dpm_xl" not in self._cache:
45
+ self._cache["dpm_xl"] = DpmXlService(self._session)
46
+ return self._cache["dpm_xl"]
47
+
48
+ @property
49
+ def syntax(self):
50
+ from dpmcore.services.syntax import SyntaxService
51
+
52
+ if "syntax" not in self._cache:
53
+ self._cache["syntax"] = SyntaxService()
54
+ return self._cache["syntax"]
55
+
56
+ @property
57
+ def semantic(self):
58
+ from dpmcore.services.semantic import SemanticService
59
+
60
+ if "semantic" not in self._cache:
61
+ self._cache["semantic"] = SemanticService(self._session)
62
+ return self._cache["semantic"]
63
+
64
+ @property
65
+ def ast_generator(self):
66
+ from dpmcore.services.ast_generator import ASTGeneratorService
67
+
68
+ if "ast_generator" not in self._cache:
69
+ self._cache["ast_generator"] = ASTGeneratorService(self._session)
70
+ return self._cache["ast_generator"]
71
+
72
+ @property
73
+ def scope_calculator(self):
74
+ from dpmcore.services.scope_calculator import ScopeCalculatorService
75
+
76
+ if "scope_calculator" not in self._cache:
77
+ self._cache["scope_calculator"] = ScopeCalculatorService(
78
+ self._session,
79
+ )
80
+ return self._cache["scope_calculator"]
81
+
82
+ # ------------------------------------------------------------------ #
83
+ # Data dictionary / explorer / hierarchy
84
+ # ------------------------------------------------------------------ #
85
+
86
+ @property
87
+ def data_dictionary(self):
88
+ from dpmcore.services.data_dictionary import DataDictionaryService
89
+
90
+ if "data_dictionary" not in self._cache:
91
+ self._cache["data_dictionary"] = DataDictionaryService(
92
+ self._session,
93
+ )
94
+ return self._cache["data_dictionary"]
95
+
96
+ @property
97
+ def explorer(self):
98
+ from dpmcore.services.explorer import ExplorerService
99
+
100
+ if "explorer" not in self._cache:
101
+ self._cache["explorer"] = ExplorerService(self._session)
102
+ return self._cache["explorer"]
103
+
104
+ @property
105
+ def hierarchy(self):
106
+ from dpmcore.services.hierarchy import HierarchyService
107
+
108
+ if "hierarchy" not in self._cache:
109
+ self._cache["hierarchy"] = HierarchyService(self._session)
110
+ return self._cache["hierarchy"]
111
+
112
+ # ------------------------------------------------------------------ #
113
+ # Structure service
114
+ # ------------------------------------------------------------------ #
115
+
116
+ @property
117
+ def structure(self):
118
+ from dpmcore.services.structure import StructureService
119
+
120
+ if "structure" not in self._cache:
121
+ self._cache["structure"] = StructureService(self._session)
122
+ return self._cache["structure"]
123
+
124
+ # ------------------------------------------------------------------ #
125
+ # Migration (requires Engine, not Session)
126
+ # ------------------------------------------------------------------ #
127
+
128
+ @property
129
+ def migration(self):
130
+ from dpmcore.services.migration import MigrationService
131
+
132
+ if "migration" not in self._cache:
133
+ self._cache["migration"] = MigrationService(self._engine)
134
+ return self._cache["migration"]
135
+
136
+
137
+ class DpmConnection:
138
+ """A connection to a DPM database.
139
+
140
+ Holds a SQLAlchemy engine + session and exposes services through
141
+ the :attr:`services` accessor.
142
+
143
+ Args:
144
+ url: SQLAlchemy connection URL.
145
+ pool_config: Optional engine keyword arguments (pool_size,
146
+ max_overflow, etc.).
147
+ """
148
+
149
+ def __init__(
150
+ self,
151
+ url: str,
152
+ pool_config: Optional[Dict[str, Any]] = None,
153
+ ) -> None:
154
+ engine_kwargs: Dict[str, Any] = {"pool_pre_ping": True}
155
+ if pool_config:
156
+ engine_kwargs.update(pool_config)
157
+
158
+ self.engine = create_engine(url, **engine_kwargs)
159
+ self._session_factory = sessionmaker(bind=self.engine)
160
+ self.session: Session = self._session_factory()
161
+ self.services = _ServiceAccessor(self.session, self.engine)
162
+
163
+ # ------------------------------------------------------------------ #
164
+ # ORM access
165
+ # ------------------------------------------------------------------ #
166
+
167
+ @property
168
+ def orm(self):
169
+ """Direct access to the SQLAlchemy session for advanced ORM
170
+ queries."""
171
+ return self.session
172
+
173
+ # ------------------------------------------------------------------ #
174
+ # Lifecycle
175
+ # ------------------------------------------------------------------ #
176
+
177
+ def close(self) -> None:
178
+ """Close the session and dispose the engine."""
179
+ self.session.close()
180
+ self.engine.dispose()
181
+
182
+ def __enter__(self) -> "DpmConnection":
183
+ return self
184
+
185
+ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
186
+ self.close()
187
+
188
+ def __repr__(self) -> str:
189
+ return f"<DpmConnection url={self.engine.url!r}>"
190
+
191
+
192
+ def connect(
193
+ url: str,
194
+ pool_config: Optional[Dict[str, Any]] = None,
195
+ ) -> DpmConnection:
196
+ """Open a connection to a DPM database.
197
+
198
+ Args:
199
+ url: A SQLAlchemy connection URL, e.g.
200
+ ``"sqlite:///path/to/dpm.db"`` or
201
+ ``"postgresql://user:pass@host:5432/db"``.
202
+ pool_config: Optional engine keyword arguments.
203
+
204
+ Returns:
205
+ A :class:`DpmConnection` instance.
206
+ """
207
+ return DpmConnection(url, pool_config=pool_config)
@@ -0,0 +1,15 @@
1
+ """Django integration for dpmcore (Mode 3).
2
+
3
+ Add ``"dpmcore.django"`` to ``INSTALLED_APPS`` to expose
4
+ read-only Django ORM models backed by the DPM database.
5
+
6
+ Requires Django 5.2+.
7
+ """
8
+
9
+ try:
10
+ import django # noqa: F401
11
+ except ImportError as exc:
12
+ raise ImportError(
13
+ "Django is required for dpmcore.django. "
14
+ "Install it with: pip install dpmcore[django]"
15
+ ) from exc