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.
- dpmcore/__init__.py +7 -0
- dpmcore/cli/__init__.py +1 -0
- dpmcore/cli/main.py +111 -0
- dpmcore/connection.py +207 -0
- dpmcore/django/__init__.py +15 -0
- dpmcore/django/admin.py +877 -0
- dpmcore/django/apps.py +12 -0
- dpmcore/django/models/__init__.py +162 -0
- dpmcore/django/models/auxiliary.py +222 -0
- dpmcore/django/models/glossary.py +624 -0
- dpmcore/django/models/infrastructure.py +845 -0
- dpmcore/django/models/operations.py +520 -0
- dpmcore/django/models/packaging.py +290 -0
- dpmcore/django/models/rendering.py +721 -0
- dpmcore/django/models/variables.py +232 -0
- dpmcore/django/routers.py +68 -0
- dpmcore/django/urls.py +8 -0
- dpmcore/dpm_xl/__init__.py +8 -0
- dpmcore/dpm_xl/ast/__init__.py +14 -0
- dpmcore/dpm_xl/ast/constructor.py +514 -0
- dpmcore/dpm_xl/ast/ml_generation.py +651 -0
- dpmcore/dpm_xl/ast/module_analyzer.py +83 -0
- dpmcore/dpm_xl/ast/module_dependencies.py +218 -0
- dpmcore/dpm_xl/ast/nodes.py +891 -0
- dpmcore/dpm_xl/ast/operands.py +612 -0
- dpmcore/dpm_xl/ast/template.py +105 -0
- dpmcore/dpm_xl/ast/visitor.py +13 -0
- dpmcore/dpm_xl/ast/where_clause.py +12 -0
- dpmcore/dpm_xl/grammar/__init__.py +18 -0
- dpmcore/dpm_xl/grammar/dpm_xlLexer.g4 +437 -0
- dpmcore/dpm_xl/grammar/dpm_xlParser.g4 +263 -0
- dpmcore/dpm_xl/grammar/generated/__init__.py +0 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlLexer.interp +432 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlLexer.py +978 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlLexer.tokens +107 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlParser.interp +251 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlParser.py +5372 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlParser.tokens +107 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlParserListener.py +750 -0
- dpmcore/dpm_xl/grammar/generated/dpm_xlParserVisitor.py +423 -0
- dpmcore/dpm_xl/grammar/generated/listeners.py +12 -0
- dpmcore/dpm_xl/model_queries.py +1944 -0
- dpmcore/dpm_xl/operators/__init__.py +19 -0
- dpmcore/dpm_xl/operators/aggregate.py +137 -0
- dpmcore/dpm_xl/operators/arithmetic.py +101 -0
- dpmcore/dpm_xl/operators/base.py +388 -0
- dpmcore/dpm_xl/operators/boolean.py +30 -0
- dpmcore/dpm_xl/operators/clause.py +200 -0
- dpmcore/dpm_xl/operators/comparison.py +69 -0
- dpmcore/dpm_xl/operators/conditional.py +422 -0
- dpmcore/dpm_xl/operators/string.py +27 -0
- dpmcore/dpm_xl/operators/time.py +53 -0
- dpmcore/dpm_xl/semantic_analyzer.py +453 -0
- dpmcore/dpm_xl/symbols.py +223 -0
- dpmcore/dpm_xl/types/__init__.py +13 -0
- dpmcore/dpm_xl/types/promotion.py +207 -0
- dpmcore/dpm_xl/types/scalar.py +326 -0
- dpmcore/dpm_xl/types/time.py +370 -0
- dpmcore/dpm_xl/utils/__init__.py +14 -0
- dpmcore/dpm_xl/utils/data_handlers.py +99 -0
- dpmcore/dpm_xl/utils/filters.py +159 -0
- dpmcore/dpm_xl/utils/operands_mapping.py +73 -0
- dpmcore/dpm_xl/utils/operator_mapping.py +90 -0
- dpmcore/dpm_xl/utils/scopes_calculator.py +503 -0
- dpmcore/dpm_xl/utils/serialization.py +813 -0
- dpmcore/dpm_xl/utils/tokens.py +180 -0
- dpmcore/dpm_xl/warning_collector.py +94 -0
- dpmcore/errors.py +545 -0
- dpmcore/orm/__init__.py +25 -0
- dpmcore/orm/auxiliary.py +213 -0
- dpmcore/orm/base.py +136 -0
- dpmcore/orm/glossary.py +872 -0
- dpmcore/orm/infrastructure.py +951 -0
- dpmcore/orm/operations.py +731 -0
- dpmcore/orm/packaging.py +379 -0
- dpmcore/orm/rendering.py +956 -0
- dpmcore/orm/variables.py +360 -0
- dpmcore/py.typed +0 -0
- dpmcore/server/__init__.py +1 -0
- dpmcore/server/app.py +170 -0
- dpmcore/server/envelope.py +68 -0
- dpmcore/server/params.py +90 -0
- dpmcore/server/routers/__init__.py +1 -0
- dpmcore/server/routers/structure.py +446 -0
- dpmcore/services/__init__.py +33 -0
- dpmcore/services/ast_generator.py +165 -0
- dpmcore/services/data_dictionary.py +191 -0
- dpmcore/services/dpm_xl.py +64 -0
- dpmcore/services/explorer.py +121 -0
- dpmcore/services/hierarchy.py +138 -0
- dpmcore/services/migration.py +290 -0
- dpmcore/services/scope_calculator.py +154 -0
- dpmcore/services/semantic.py +125 -0
- dpmcore/services/structure.py +498 -0
- dpmcore/services/syntax.py +96 -0
- dpmcore-0.0.1.dist-info/METADATA +394 -0
- dpmcore-0.0.1.dist-info/RECORD +100 -0
- dpmcore-0.0.1.dist-info/WHEEL +4 -0
- dpmcore-0.0.1.dist-info/entry_points.txt +3 -0
- dpmcore-0.0.1.dist-info/licenses/LICENSE +674 -0
dpmcore/__init__.py
ADDED
dpmcore/cli/__init__.py
ADDED
|
@@ -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
|