sqlrules 0.2.0__py3-none-any.whl → 0.3.0__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.
sqlrules/__init__.py CHANGED
@@ -5,6 +5,7 @@ from sqlrules.errors import (
5
5
  InvalidModelError,
6
6
  InvalidTranslatorError,
7
7
  MissingColumnError,
8
+ PluginError,
8
9
  RegistryError,
9
10
  SQLRulesError,
10
11
  TranslatorError,
@@ -18,11 +19,13 @@ from sqlrules.ir import (
18
19
  FieldIR,
19
20
  ModelIR,
20
21
  )
22
+ from sqlrules.plugins import PLUGIN_API_VERSION, SQLRulesPlugin
21
23
  from sqlrules.translators import SQLRulesWarning
22
24
 
23
- __version__ = "0.2.0"
25
+ __version__ = "0.3.0"
24
26
 
25
27
  __all__ = [
28
+ "PLUGIN_API_VERSION",
26
29
  "CompilationContext",
27
30
  "Compiler",
28
31
  "ConfigurationError",
@@ -35,8 +38,10 @@ __all__ = [
35
38
  "InvalidTranslatorError",
36
39
  "MissingColumnError",
37
40
  "ModelIR",
41
+ "PluginError",
38
42
  "RegistryError",
39
43
  "SQLRulesError",
44
+ "SQLRulesPlugin",
40
45
  "SQLRulesWarning",
41
46
  "TranslatorError",
42
47
  "UnsupportedConstraintError",
sqlrules/compiler.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections.abc import Mapping
3
+ from collections.abc import Mapping, Sequence
4
4
  from itertools import chain
5
5
  from typing import Any
6
6
 
@@ -18,8 +18,10 @@ from sqlrules.ir import (
18
18
  DiagnosticsCollector,
19
19
  FieldIR,
20
20
  ModelIR,
21
+ OnConflict,
21
22
  OnUnsupported,
22
23
  )
24
+ from sqlrules.plugins import SQLRulesPlugin, validate_plugin
23
25
  from sqlrules.translators import TranslatorRegistry, default_registry
24
26
 
25
27
  RulesDict = dict[str, list[ColumnElement[bool]]]
@@ -30,20 +32,45 @@ class Compiler:
30
32
  self,
31
33
  *,
32
34
  registry: TranslatorRegistry | None = None,
35
+ plugins: Sequence[SQLRulesPlugin] | None = None,
36
+ on_conflict: OnConflict = "raise",
33
37
  on_unsupported: OnUnsupported = "raise",
38
+ dialect: str | None = None,
34
39
  cache: bool = True,
35
40
  model_cache: ModelIRCache | None = None,
36
41
  ) -> None:
37
42
  if on_unsupported not in {"raise", "warn", "ignore"}:
38
43
  raise ConfigurationError(option="on_unsupported", value=on_unsupported)
39
- self.registry = registry or default_registry()
44
+ if on_conflict not in {"raise", "replace", "ignore"}:
45
+ raise ConfigurationError(option="on_conflict", value=on_conflict)
46
+
47
+ base = registry if registry is not None else default_registry()
48
+ plugin_list = list(plugins or ())
49
+ resolved: TranslatorRegistry
50
+ if plugin_list:
51
+ # Copy so plugins cannot mutate a caller-owned or shared registry.
52
+ aware = _PluginAwareRegistry(
53
+ base.copy(),
54
+ default_on_conflict=on_conflict,
55
+ )
56
+ for plugin in plugin_list:
57
+ validate_plugin(plugin).register(aware)
58
+ # Freeze to a plain registry after registration.
59
+ resolved = aware.copy()
60
+ else:
61
+ resolved = base
62
+ self.registry = resolved
63
+
40
64
  self.on_unsupported = on_unsupported
65
+ self.on_conflict = on_conflict
66
+ self.dialect = dialect
41
67
  self.cache_enabled = cache
42
68
  self._model_cache = model_cache if model_cache is not None else default_cache()
43
69
  self._collector = DiagnosticsCollector()
44
70
  self.context = CompilationContext(
45
71
  on_unsupported=on_unsupported,
46
72
  collector=self._collector,
73
+ dialect=dialect,
47
74
  )
48
75
 
49
76
  @property
@@ -83,6 +110,7 @@ class Compiler:
83
110
  self.context = CompilationContext(
84
111
  on_unsupported=self.on_unsupported,
85
112
  collector=self._collector,
113
+ dialect=self.dialect,
86
114
  )
87
115
 
88
116
  rules: RulesDict = {}
@@ -120,6 +148,43 @@ class Compiler:
120
148
  return self.bind(self.compile_model(model), table, column_map=column_map)
121
149
 
122
150
 
151
+ class _PluginAwareRegistry(TranslatorRegistry):
152
+ """Registry that applies Compiler.on_conflict as the default for register()."""
153
+
154
+ def __init__(
155
+ self,
156
+ base: TranslatorRegistry,
157
+ *,
158
+ default_on_conflict: OnConflict,
159
+ ) -> None:
160
+ super().__init__()
161
+ self._translators = dict(base._translators)
162
+ self._default_on_conflict = default_on_conflict
163
+
164
+ def register(
165
+ self,
166
+ operator_name: str,
167
+ translator: Any,
168
+ *,
169
+ replace: bool = False,
170
+ ) -> None:
171
+ on_conflict: OnConflict = "replace" if replace else self._default_on_conflict
172
+ self.register_constraint(operator_name, translator, on_conflict=on_conflict)
173
+
174
+ def register_constraint(
175
+ self,
176
+ operator_name: str,
177
+ translator: Any,
178
+ *,
179
+ on_conflict: OnConflict | None = None,
180
+ ) -> None:
181
+ super().register_constraint(
182
+ operator_name,
183
+ translator,
184
+ on_conflict=on_conflict if on_conflict is not None else self._default_on_conflict,
185
+ )
186
+
187
+
123
188
  def compile(
124
189
  model: type[BaseModel],
125
190
  table: Any,
@@ -0,0 +1,146 @@
1
+ """Plugin conformance helpers for official and third-party plugins."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextlib
6
+ from typing import Annotated, Any
7
+
8
+ from pydantic import BaseModel, Field
9
+ from sqlalchemy import Column, MetaData, String, Table
10
+ from sqlalchemy.sql.elements import ColumnElement
11
+
12
+ from sqlrules.compiler import Compiler
13
+ from sqlrules.errors import PluginError, RegistryError
14
+ from sqlrules.plugins import PLUGIN_API_VERSION, SQLRulesPlugin, validate_plugin
15
+ from sqlrules.translators import TranslatorRegistry, default_registry
16
+
17
+
18
+ def assert_plugin_api_compatible(plugin: SQLRulesPlugin) -> None:
19
+ """Raise PluginError if the plugin does not match the current plugin API."""
20
+ validate_plugin(plugin)
21
+
22
+
23
+ def assert_register_conflict_raise(plugin: SQLRulesPlugin) -> None:
24
+ """Registering the same plugin twice with on_conflict='raise' must fail.
25
+
26
+ Plugins that only add new operators (not in the default registry) are
27
+ registered twice against a fresh default registry; the second pass must
28
+ raise RegistryError for any overlapping operator.
29
+ """
30
+ validate_plugin(plugin)
31
+ registry = default_registry()
32
+ plugin.register(registry)
33
+ try:
34
+ plugin.register(registry)
35
+ except RegistryError:
36
+ return
37
+ raise AssertionError(
38
+ f"Plugin {plugin.name!r} did not raise RegistryError on duplicate registration"
39
+ )
40
+
41
+
42
+ def assert_register_idempotent_ignore(plugin: SQLRulesPlugin) -> None:
43
+ """Registering twice with on_conflict='ignore' must succeed and keep builtins."""
44
+ validate_plugin(plugin)
45
+ builtins = default_registry().operators()
46
+ compiler = Compiler(plugins=[plugin], on_conflict="ignore", cache=False)
47
+ first_ops = compiler.registry.operators()
48
+ # Re-apply via a second compiler with the same plugin.
49
+ compiler2 = Compiler(
50
+ registry=compiler.registry.copy(),
51
+ plugins=[plugin],
52
+ on_conflict="ignore",
53
+ cache=False,
54
+ )
55
+ assert compiler2.registry.operators() == first_ops
56
+ assert builtins <= compiler2.registry.operators()
57
+
58
+
59
+ def assert_builtins_preserved(
60
+ plugin: SQLRulesPlugin,
61
+ *,
62
+ on_conflict: str = "raise",
63
+ ) -> None:
64
+ """Plugins must not drop built-in operators."""
65
+ validate_plugin(plugin)
66
+ builtins = default_registry().operators()
67
+ compiler = Compiler(plugins=[plugin], on_conflict=on_conflict, cache=False) # type: ignore[arg-type]
68
+ missing = builtins - compiler.registry.operators()
69
+ if missing:
70
+ raise AssertionError(
71
+ f"Plugin {plugin.name!r} removed built-in operators: {sorted(missing)}"
72
+ )
73
+
74
+
75
+ def assert_translates_operator(
76
+ plugin: SQLRulesPlugin,
77
+ *,
78
+ operator: str,
79
+ model: type[BaseModel] | None = None,
80
+ table: Any | None = None,
81
+ field: str = "name",
82
+ ) -> ColumnElement[bool]:
83
+ """Compile with the plugin and assert ``operator`` yields a ColumnElement."""
84
+ validate_plugin(plugin)
85
+
86
+ if model is None:
87
+
88
+ class PatternFilter(BaseModel):
89
+ name: Annotated[str, Field(pattern=r"^a")]
90
+
91
+ model = PatternFilter
92
+
93
+ if table is None:
94
+ table = Table("items", MetaData(), Column("name", String))
95
+
96
+ compiler = Compiler(plugins=[plugin], on_conflict="replace", cache=False)
97
+ if operator not in compiler.registry:
98
+ raise AssertionError(f"Plugin {plugin.name!r} did not register operator {operator!r}")
99
+
100
+ rules = compiler.compile(model, table)
101
+ if field not in rules or not rules[field]:
102
+ raise AssertionError(f"Plugin {plugin.name!r}: expected expressions for field {field!r}")
103
+
104
+ expression = rules[field][0]
105
+ if not isinstance(expression, ColumnElement):
106
+ raise AssertionError(
107
+ f"Plugin {plugin.name!r}: translator for {operator!r} "
108
+ f"returned {type(expression)!r}, expected ColumnElement"
109
+ )
110
+
111
+ # Deterministic compile smoke check (dialect-neutral string form).
112
+ compiled_a = str(expression.compile(compile_kwargs={"literal_binds": True}))
113
+ compiled_b = str(expression.compile(compile_kwargs={"literal_binds": True}))
114
+ if compiled_a != compiled_b:
115
+ raise AssertionError(f"Plugin {plugin.name!r}: non-deterministic compile for {operator!r}")
116
+ return expression
117
+
118
+
119
+ def run_basic_conformance(plugin: SQLRulesPlugin, *, operator: str = "pattern") -> None:
120
+ """Run the standard conformance checks for a dialect/translator plugin."""
121
+ if getattr(plugin, "api_version", None) != PLUGIN_API_VERSION:
122
+ raise PluginError(
123
+ message=(
124
+ f"Plugin {getattr(plugin, 'name', plugin)!r} api_version mismatch "
125
+ f"(expected {PLUGIN_API_VERSION!r})."
126
+ ),
127
+ plugin=plugin,
128
+ )
129
+ assert_plugin_api_compatible(plugin)
130
+ assert_builtins_preserved(plugin, on_conflict="replace")
131
+ assert_register_idempotent_ignore(plugin)
132
+ assert_translates_operator(plugin, operator=operator)
133
+
134
+ # Conflict under raise: only meaningful if the plugin re-registers an op
135
+ # already present after the first registration.
136
+ registry = default_registry()
137
+ plugin.register(registry)
138
+ with contextlib.suppress(RegistryError):
139
+ # If the plugin uses on_conflict="replace" internally, duplicate
140
+ # registration may succeed; that is acceptable for dialect overrides.
141
+ plugin.register(registry)
142
+
143
+
144
+ def registry_without_builtins() -> TranslatorRegistry:
145
+ """Empty registry for isolated translator unit tests."""
146
+ return TranslatorRegistry()
sqlrules/errors.py CHANGED
@@ -85,12 +85,28 @@ class ConfigurationError(SQLRulesError):
85
85
  value: Any
86
86
 
87
87
  def __str__(self) -> str:
88
- return (
89
- f"Invalid configuration value for {self.option!r}: {self.value!r}. "
90
- "Use one of: 'raise', 'warn', 'ignore'."
91
- if self.option == "on_unsupported"
92
- else f"Invalid configuration value for {self.option!r}: {self.value!r}."
93
- )
88
+ if self.option == "on_unsupported":
89
+ return (
90
+ f"Invalid configuration value for {self.option!r}: {self.value!r}. "
91
+ "Use one of: 'raise', 'warn', 'ignore'."
92
+ )
93
+ if self.option == "on_conflict":
94
+ return (
95
+ f"Invalid configuration value for {self.option!r}: {self.value!r}. "
96
+ "Use one of: 'raise', 'replace', 'ignore'."
97
+ )
98
+ return f"Invalid configuration value for {self.option!r}: {self.value!r}."
99
+
100
+
101
+ @dataclass(slots=True)
102
+ class PluginError(SQLRulesError):
103
+ """Raised when a plugin fails validation or registration."""
104
+
105
+ message: str
106
+ plugin: Any = None
107
+
108
+ def __str__(self) -> str:
109
+ return self.message
94
110
 
95
111
 
96
112
  class InternalCompilerError(SQLRulesError):
sqlrules/ir.py CHANGED
@@ -4,6 +4,7 @@ from dataclasses import dataclass, field
4
4
  from typing import Any, Literal
5
5
 
6
6
  OnUnsupported = Literal["raise", "warn", "ignore"]
7
+ OnConflict = Literal["raise", "replace", "ignore"]
7
8
  DiagnosticSeverity = Literal["warning", "info"]
8
9
 
9
10
 
@@ -30,6 +31,7 @@ class Diagnostic:
30
31
  operator: str
31
32
  value: Any = None
32
33
  message: str = ""
34
+ code: str = ""
33
35
 
34
36
 
35
37
  @dataclass(slots=True)
@@ -52,6 +54,7 @@ class DiagnosticsCollector:
52
54
  class CompilationContext:
53
55
  on_unsupported: OnUnsupported = "raise"
54
56
  collector: DiagnosticsCollector | None = None
57
+ dialect: str | None = None
55
58
 
56
59
  def record(
57
60
  self,
@@ -61,6 +64,7 @@ class CompilationContext:
61
64
  operator: str,
62
65
  value: Any = None,
63
66
  message: str = "",
67
+ code: str = "",
64
68
  ) -> None:
65
69
  if self.collector is None:
66
70
  return
@@ -71,6 +75,7 @@ class CompilationContext:
71
75
  operator=operator,
72
76
  value=value,
73
77
  message=message,
78
+ code=code,
74
79
  )
75
80
  )
76
81
 
sqlrules/plugins.py ADDED
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Protocol, runtime_checkable
4
+
5
+ from sqlrules.errors import PluginError
6
+ from sqlrules.translators import TranslatorRegistry
7
+
8
+ PLUGIN_API_VERSION = "1"
9
+
10
+
11
+ @runtime_checkable
12
+ class SQLRulesPlugin(Protocol):
13
+ """Explicit extension that registers translators onto a registry."""
14
+
15
+ name: str
16
+ api_version: str
17
+
18
+ def register(self, registry: TranslatorRegistry) -> None:
19
+ """Register constraint translators on ``registry``."""
20
+
21
+
22
+ def validate_plugin(plugin: Any) -> SQLRulesPlugin:
23
+ """Validate a plugin object against the versioned plugin API."""
24
+ if not isinstance(plugin, SQLRulesPlugin):
25
+ raise PluginError(
26
+ message=(
27
+ f"Plugin {plugin!r} does not implement SQLRulesPlugin "
28
+ "(requires name, api_version, and register(registry))."
29
+ ),
30
+ plugin=plugin,
31
+ )
32
+
33
+ name = getattr(plugin, "name", None)
34
+ if not isinstance(name, str) or not name:
35
+ raise PluginError(
36
+ message=f"Plugin {plugin!r} must declare a non-empty string name.",
37
+ plugin=plugin,
38
+ )
39
+
40
+ api_version = getattr(plugin, "api_version", None)
41
+ if api_version != PLUGIN_API_VERSION:
42
+ raise PluginError(
43
+ message=(
44
+ f"Plugin {name!r} declares api_version={api_version!r}, "
45
+ f"but SQLRules plugin API is {PLUGIN_API_VERSION!r}."
46
+ ),
47
+ plugin=plugin,
48
+ )
49
+
50
+ register = getattr(plugin, "register", None)
51
+ if not callable(register):
52
+ raise PluginError(
53
+ message=f"Plugin {name!r} must provide a callable register(registry) method.",
54
+ plugin=plugin,
55
+ )
56
+
57
+ return plugin
sqlrules/translators.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import inspect
3
4
  import operator
4
5
  import sys
5
6
  import types
@@ -10,8 +11,13 @@ from typing import Any, cast
10
11
  from sqlalchemy import func
11
12
  from sqlalchemy.sql.elements import ColumnElement
12
13
 
13
- from sqlrules.errors import RegistryError, TranslatorError, UnsupportedConstraintError
14
- from sqlrules.ir import CompilationContext, Constraint
14
+ from sqlrules.errors import (
15
+ InvalidTranslatorError,
16
+ RegistryError,
17
+ TranslatorError,
18
+ UnsupportedConstraintError,
19
+ )
20
+ from sqlrules.ir import CompilationContext, Constraint, OnConflict
15
21
 
16
22
  Translator = Callable[[Constraint, ColumnElement[Any], CompilationContext], ColumnElement[bool]]
17
23
 
@@ -93,6 +99,32 @@ def _in_values(
93
99
  return cast(ColumnElement[bool], column.in_(constraint.value))
94
100
 
95
101
 
102
+ def _validate_translator(operator_name: str, translator: Any) -> Translator:
103
+ if not callable(translator):
104
+ raise InvalidTranslatorError(operator=operator_name, translator=translator)
105
+ try:
106
+ signature = inspect.signature(translator)
107
+ except (TypeError, ValueError):
108
+ return cast(Translator, translator)
109
+
110
+ params = list(signature.parameters.values())
111
+ if any(p.kind == inspect.Parameter.VAR_POSITIONAL for p in params):
112
+ return cast(Translator, translator)
113
+
114
+ positional = [
115
+ p
116
+ for p in params
117
+ if p.kind
118
+ in (
119
+ inspect.Parameter.POSITIONAL_ONLY,
120
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
121
+ )
122
+ ]
123
+ if len(positional) < 3:
124
+ raise InvalidTranslatorError(operator=operator_name, translator=translator)
125
+ return cast(Translator, translator)
126
+
127
+
96
128
  class TranslatorRegistry:
97
129
  def __init__(self) -> None:
98
130
  self._translators: dict[str, Translator] = {}
@@ -104,13 +136,46 @@ class TranslatorRegistry:
104
136
  *,
105
137
  replace: bool = False,
106
138
  ) -> None:
107
- if not replace and operator_name in self._translators:
108
- raise RegistryError(f"Translator for {operator_name!r} is already registered.")
109
- self._translators[operator_name] = translator
139
+ self.register_constraint(
140
+ operator_name,
141
+ translator,
142
+ on_conflict="replace" if replace else "raise",
143
+ )
144
+
145
+ def register_constraint(
146
+ self,
147
+ operator_name: str,
148
+ translator: Translator,
149
+ *,
150
+ on_conflict: OnConflict = "raise",
151
+ ) -> None:
152
+ if on_conflict not in {"raise", "replace", "ignore"}:
153
+ raise RegistryError(
154
+ f"Invalid on_conflict value {on_conflict!r}. "
155
+ "Use one of: 'raise', 'replace', 'ignore'."
156
+ )
157
+ validated = _validate_translator(operator_name, translator)
158
+ if operator_name in self._translators:
159
+ if on_conflict == "raise":
160
+ raise RegistryError(f"Translator for {operator_name!r} is already registered.")
161
+ if on_conflict == "ignore":
162
+ return
163
+ self._translators[operator_name] = validated
110
164
 
111
165
  def lookup(self, operator_name: str) -> Translator | None:
112
166
  return self._translators.get(operator_name)
113
167
 
168
+ def operators(self) -> frozenset[str]:
169
+ return frozenset(self._translators)
170
+
171
+ def __contains__(self, operator_name: object) -> bool:
172
+ return isinstance(operator_name, str) and operator_name in self._translators
173
+
174
+ def copy(self) -> TranslatorRegistry:
175
+ clone = TranslatorRegistry()
176
+ clone._translators = dict(self._translators)
177
+ return clone
178
+
114
179
  def translate(
115
180
  self,
116
181
  constraint: Constraint,
@@ -137,6 +202,7 @@ class TranslatorRegistry:
137
202
  operator=constraint.operator,
138
203
  value=constraint.value,
139
204
  message=message,
205
+ code="unsupported_constraint",
140
206
  )
141
207
  warnings.warn(message, SQLRulesWarning, stacklevel=_warning_stacklevel())
142
208
  else:
@@ -146,6 +212,7 @@ class TranslatorRegistry:
146
212
  operator=constraint.operator,
147
213
  value=constraint.value,
148
214
  message=message,
215
+ code="unsupported_constraint",
149
216
  )
150
217
  return None
151
218
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqlrules
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Compile constrained Pydantic models into SQLAlchemy WHERE-rule dictionaries.
5
5
  Project-URL: Homepage, https://github.com/eddiethedean/sqlrules
6
6
  Project-URL: Documentation, https://github.com/eddiethedean/sqlrules#readme
@@ -81,7 +81,22 @@ pip install sqlrules
81
81
 
82
82
  Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
83
83
 
84
- ## Supported constraints (0.2)
84
+ Optional dialect plugins:
85
+
86
+ ```bash
87
+ pip install sqlrules-postgresql # pattern → PostgreSQL ~
88
+ pip install sqlrules-sqlite # pattern → SQLite REGEXP
89
+ ```
90
+
91
+ ```python
92
+ from sqlrules import Compiler
93
+ from sqlrules_postgresql import PostgresPlugin
94
+
95
+ compiler = Compiler(plugins=[PostgresPlugin()], dialect="postgresql")
96
+ rules = compiler.compile(UserFilter, users)
97
+ ```
98
+
99
+ ## Supported constraints (0.3)
85
100
 
86
101
  | Constraint | SQLAlchemy expression |
87
102
  |---|---|
@@ -92,6 +107,8 @@ Requires Python 3.10+, Pydantic v2, and SQLAlchemy 2.x.
92
107
  | `Enum` | `column.in_(...)` |
93
108
 
94
109
  `pattern` is extracted into IR but has no portable core translator.
110
+ Use a custom registry translator or a dialect plugin
111
+ (`sqlrules-postgresql`, `sqlrules-sqlite`).
95
112
 
96
113
  Unsupported constraints raise `UnsupportedConstraintError` by default.
97
114
  Use `on_unsupported="warn"` or `"ignore"` to change that policy for unknown
@@ -103,6 +120,14 @@ constraint operators (unsupported types always raise).
103
120
  sqlrules.compile(model, table, *, column_map=None, on_unsupported="raise", cache=True)
104
121
  sqlrules.where(rules) # flatten all expressions
105
122
  sqlrules.flatten(rules) # alias of where()
123
+
124
+ compiler = sqlrules.Compiler(
125
+ plugins=[...], # optional SQLRulesPlugin instances
126
+ on_conflict="raise", # raise | replace | ignore
127
+ dialect=None, # optional hint for translators
128
+ on_unsupported="raise",
129
+ cache=True,
130
+ )
106
131
  ```
107
132
 
108
133
  ## Non-goals
@@ -115,13 +140,15 @@ constraints into SQLAlchemy expressions.
115
140
 
116
141
  See [`docs/index.md`](docs/index.md) for the full documentation set,
117
142
  including the [spec](docs/SPEC.md), [API](docs/API.md),
118
- [architecture](docs/ARCHITECTURE.md), and [roadmap](docs/ROADMAP.md).
143
+ [plugin system](docs/PLUGIN_SYSTEM.md), [architecture](docs/ARCHITECTURE.md),
144
+ and [roadmap](docs/ROADMAP.md).
119
145
 
120
146
  ## Development
121
147
 
122
148
  ```bash
123
149
  pip install -e ".[dev]"
124
- pytest
150
+ pip install -e packages/sqlrules-postgresql -e packages/sqlrules-sqlite
151
+ pytest tests packages/sqlrules-postgresql/tests packages/sqlrules-sqlite/tests
125
152
  ruff check .
126
153
  mypy src/sqlrules
127
154
  python -m benchmarks.bench_compile
@@ -0,0 +1,16 @@
1
+ sqlrules/__init__.py,sha256=IEIU4ESvzydICwS2CnqXzIz9KA5PiQvHw-FlMybfkvM,1130
2
+ sqlrules/cache.py,sha256=9NBDunUMvQR3bsx9QxIiO8L8JNbpI1tdLlFNapUZMj8,1051
3
+ sqlrules/columns.py,sha256=TcDhFG8wxgKC3-kwXhOBP27Ekrlfncm-WWrUkMLCplo,2428
4
+ sqlrules/compiler.py,sha256=sVlpcP5qv-BUjKSNwd4Q6I3aQvqVOgDo89EAwxTqo5I,6799
5
+ sqlrules/conformance.py,sha256=QV3lTfVtj0vcWUfP8NO_RD7HBon-sANSGdIpVttn_xM,5469
6
+ sqlrules/constraints.py,sha256=YcI2TstNoW9GGR4TfoWBzMlmKKNPsqNjTdUr3Cd3rk8,12349
7
+ sqlrules/errors.py,sha256=qcSCcnL19kvfzAd1eMN1pVVNbV3cg1WLb94uGna8w-M,2989
8
+ sqlrules/inspectors.py,sha256=mEi7ixA1aQe7lyZtcFvLHVqVsAXt_tm60YNgJrYbrp8,1700
9
+ sqlrules/ir.py,sha256=ekZ6HLxrJofGvlxZwh2BZty5oV1uzJQjSfZf0GXb6z8,2097
10
+ sqlrules/plugins.py,sha256=ClXVdISpgNzVQkQCJk9ptgnXxqWLwi99EBcNP9wYXyI,1740
11
+ sqlrules/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ sqlrules/translators.py,sha256=aw8Q8hgfNUqdUEosUXgMW4IqTTBNUtSA-Yc2aCiPKNs,7949
13
+ sqlrules-0.3.0.dist-info/METADATA,sha256=DcOg6uAGJlk2brXQloOv09vEWJbBvOfu1EfMsP9mlAY,4898
14
+ sqlrules-0.3.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
15
+ sqlrules-0.3.0.dist-info/licenses/LICENSE,sha256=cLMlkCH6RhjWAkXBWR7LazV98TOG0WkPtCKB5zqqkAs,1078
16
+ sqlrules-0.3.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- sqlrules/__init__.py,sha256=33UBpq4vkVgxLh6m9EU4RNM1gFRdnnZwXtYuZHJjvY4,982
2
- sqlrules/cache.py,sha256=9NBDunUMvQR3bsx9QxIiO8L8JNbpI1tdLlFNapUZMj8,1051
3
- sqlrules/columns.py,sha256=TcDhFG8wxgKC3-kwXhOBP27Ekrlfncm-WWrUkMLCplo,2428
4
- sqlrules/compiler.py,sha256=UgtZfhCGFyt1bMu25koGaCkcd-Z9Lcer5KDF6JIe-bM,4621
5
- sqlrules/constraints.py,sha256=YcI2TstNoW9GGR4TfoWBzMlmKKNPsqNjTdUr3Cd3rk8,12349
6
- sqlrules/errors.py,sha256=SOfRXfhwb9wIyrPjukVP_alKEV83hlORyngN0NuCD_8,2533
7
- sqlrules/inspectors.py,sha256=mEi7ixA1aQe7lyZtcFvLHVqVsAXt_tm60YNgJrYbrp8,1700
8
- sqlrules/ir.py,sha256=Lq53l4ELGzrYZ0ZqM_fqeLau1kSrod91zjEUIO7_JE4,1945
9
- sqlrules/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- sqlrules/translators.py,sha256=f21pBuDk6r5xKstnEgRiL8ZubFjo5QvbeHJDzOY10nU,5818
11
- sqlrules-0.2.0.dist-info/METADATA,sha256=28Q8NuNDJLpREon9qGQruaR66_34QQtbTsy4Pr-2kIE,3988
12
- sqlrules-0.2.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
13
- sqlrules-0.2.0.dist-info/licenses/LICENSE,sha256=cLMlkCH6RhjWAkXBWR7LazV98TOG0WkPtCKB5zqqkAs,1078
14
- sqlrules-0.2.0.dist-info/RECORD,,