opa-golib-python-bindings 0.2.0__tar.gz → 0.3.0__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.
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/PKG-INFO +47 -1
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/README.md +45 -0
- opa_golib_python_bindings-0.3.0/go/compile.go +106 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/go.mod +3 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/go.sum +11 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/pyproject.toml +2 -2
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/src/opa_bindings/_native.py +12 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/src/opa_bindings/engine.py +56 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/.gitignore +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/LICENSE +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/Makefile +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/bridge.go +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/bridge_call.c +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/builtins.go +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/merge.go +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/go/trace.go +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/hatch_build.py +0 -0
- {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/src/opa_bindings/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: opa-golib-python-bindings
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Python bindings for the OPA (Open Policy Agent) Rego engine via a Go c-shared library
|
|
5
5
|
Project-URL: Homepage, https://github.com/phi1010/opa-golib-python-bindings
|
|
6
6
|
Project-URL: Repository, https://github.com/phi1010/opa-golib-python-bindings
|
|
@@ -21,6 +21,7 @@ Classifier: Topic :: Software Development :: Libraries
|
|
|
21
21
|
Requires-Python: >=3.14
|
|
22
22
|
Provides-Extra: dev
|
|
23
23
|
Requires-Dist: pytest; extra == 'dev'
|
|
24
|
+
Requires-Dist: pyyaml; extra == 'dev'
|
|
24
25
|
Description-Content-Type: text/markdown
|
|
25
26
|
|
|
26
27
|
# opa-golib-python-bindings
|
|
@@ -82,6 +83,51 @@ Notes:
|
|
|
82
83
|
statement may appear multiple times (`Redo` on backtracking), and tracing
|
|
83
84
|
slows evaluation, so keep it opt-in per call. Coverage only records *which*
|
|
84
85
|
statements were evaluated; traces are how to see their results.
|
|
86
|
+
- `compile_filters` partially evaluates a query and translates the residual
|
|
87
|
+
policy into a data filter (OPA's Compile-API / data-filter machinery):
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
engine.add_policy("filters.rego", """
|
|
91
|
+
package filters
|
|
92
|
+
|
|
93
|
+
include if input.fruits.colour == "green"
|
|
94
|
+
include if {
|
|
95
|
+
input.fruits.name == "banana"
|
|
96
|
+
input.user == "admin"
|
|
97
|
+
}
|
|
98
|
+
""")
|
|
99
|
+
engine.compile_filters(
|
|
100
|
+
"data.filters.include",
|
|
101
|
+
{"user": "admin"}, # known input
|
|
102
|
+
unknowns=["input.fruits"], # left symbolic
|
|
103
|
+
target="sql", dialect="postgresql",
|
|
104
|
+
)
|
|
105
|
+
# -> {"query": "WHERE (fruits.colour = E'green' OR fruits.name = E'banana')",
|
|
106
|
+
# "masks": None}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`target="sql"` (dialects `postgresql`, `mysql`, `sqlserver`, `sqlite`)
|
|
110
|
+
yields a WHERE clause string; `target="ucast"` (dialects `all`, `prisma`,
|
|
111
|
+
`linq`, or `""`) yields a UCAST condition dict (the `ucast.json` wire
|
|
112
|
+
format). `query` is `None` when the policy can never match and `""`/`{}`
|
|
113
|
+
when it always matches. `mappings` renames tables/columns (e.g.
|
|
114
|
+
`{"fruits": {"$self": "fruit_table", "colour": "col"}}`), and `mask_rule`
|
|
115
|
+
names a rule evaluated to produce column masks (returned under `"masks"`).
|
|
116
|
+
Residual conditions that cannot be expressed for the chosen target raise
|
|
117
|
+
`OpaError(code="compile_error")`.
|
|
118
|
+
|
|
119
|
+
Unknown refs must have the shape `input.<table>.<column>` — exactly two
|
|
120
|
+
segments after `input`, whatever the declared unknown boundary is, and for
|
|
121
|
+
every target/dialect (`ucast`/`all` included; `mappings` cannot deepen it).
|
|
122
|
+
So `unknowns=["input.item"]` permits only `input.item.<column>`, while the
|
|
123
|
+
bare `unknowns=["input"]` permits `input.<table>.<column>`. Deeper refs
|
|
124
|
+
like `input.item.attrs.price.value` fail with `pe_fragment_error: invalid
|
|
125
|
+
ref operand`, so nested documents (e.g. an EAV entity with per-attribute
|
|
126
|
+
type/value objects, or per-locale value objects) must be flattened into
|
|
127
|
+
columns (`input.attr.value_number`, `input.attr.value_de`, ...). Dynamic
|
|
128
|
+
column choice is fine as long as the key is known at compile time:
|
|
129
|
+
`input.attr[sprintf("value_%s", [input.locale])]` resolves to a single
|
|
130
|
+
column during partial evaluation.
|
|
85
131
|
- Rego `print(...)` output is captured per evaluation: set `engine.print_handler`
|
|
86
132
|
to a `callable(message, location)` to receive it (default: written to stderr);
|
|
87
133
|
`engine.last_prints` holds the `(message, location)` pairs of the last eval.
|
|
@@ -57,6 +57,51 @@ Notes:
|
|
|
57
57
|
statement may appear multiple times (`Redo` on backtracking), and tracing
|
|
58
58
|
slows evaluation, so keep it opt-in per call. Coverage only records *which*
|
|
59
59
|
statements were evaluated; traces are how to see their results.
|
|
60
|
+
- `compile_filters` partially evaluates a query and translates the residual
|
|
61
|
+
policy into a data filter (OPA's Compile-API / data-filter machinery):
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
engine.add_policy("filters.rego", """
|
|
65
|
+
package filters
|
|
66
|
+
|
|
67
|
+
include if input.fruits.colour == "green"
|
|
68
|
+
include if {
|
|
69
|
+
input.fruits.name == "banana"
|
|
70
|
+
input.user == "admin"
|
|
71
|
+
}
|
|
72
|
+
""")
|
|
73
|
+
engine.compile_filters(
|
|
74
|
+
"data.filters.include",
|
|
75
|
+
{"user": "admin"}, # known input
|
|
76
|
+
unknowns=["input.fruits"], # left symbolic
|
|
77
|
+
target="sql", dialect="postgresql",
|
|
78
|
+
)
|
|
79
|
+
# -> {"query": "WHERE (fruits.colour = E'green' OR fruits.name = E'banana')",
|
|
80
|
+
# "masks": None}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`target="sql"` (dialects `postgresql`, `mysql`, `sqlserver`, `sqlite`)
|
|
84
|
+
yields a WHERE clause string; `target="ucast"` (dialects `all`, `prisma`,
|
|
85
|
+
`linq`, or `""`) yields a UCAST condition dict (the `ucast.json` wire
|
|
86
|
+
format). `query` is `None` when the policy can never match and `""`/`{}`
|
|
87
|
+
when it always matches. `mappings` renames tables/columns (e.g.
|
|
88
|
+
`{"fruits": {"$self": "fruit_table", "colour": "col"}}`), and `mask_rule`
|
|
89
|
+
names a rule evaluated to produce column masks (returned under `"masks"`).
|
|
90
|
+
Residual conditions that cannot be expressed for the chosen target raise
|
|
91
|
+
`OpaError(code="compile_error")`.
|
|
92
|
+
|
|
93
|
+
Unknown refs must have the shape `input.<table>.<column>` — exactly two
|
|
94
|
+
segments after `input`, whatever the declared unknown boundary is, and for
|
|
95
|
+
every target/dialect (`ucast`/`all` included; `mappings` cannot deepen it).
|
|
96
|
+
So `unknowns=["input.item"]` permits only `input.item.<column>`, while the
|
|
97
|
+
bare `unknowns=["input"]` permits `input.<table>.<column>`. Deeper refs
|
|
98
|
+
like `input.item.attrs.price.value` fail with `pe_fragment_error: invalid
|
|
99
|
+
ref operand`, so nested documents (e.g. an EAV entity with per-attribute
|
|
100
|
+
type/value objects, or per-locale value objects) must be flattened into
|
|
101
|
+
columns (`input.attr.value_number`, `input.attr.value_de`, ...). Dynamic
|
|
102
|
+
column choice is fine as long as the key is known at compile time:
|
|
103
|
+
`input.attr[sprintf("value_%s", [input.locale])]` resolves to a single
|
|
104
|
+
column during partial evaluation.
|
|
60
105
|
- Rego `print(...)` output is captured per evaluation: set `engine.print_handler`
|
|
61
106
|
to a `callable(message, location)` to receive it (default: written to stderr);
|
|
62
107
|
`engine.last_prints` holds the `(message, location)` pairs of the last eval.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
#include <stdlib.h>
|
|
5
|
+
*/
|
|
6
|
+
import "C"
|
|
7
|
+
|
|
8
|
+
import (
|
|
9
|
+
"context"
|
|
10
|
+
"encoding/json"
|
|
11
|
+
|
|
12
|
+
"github.com/open-policy-agent/opa/v1/ast"
|
|
13
|
+
"github.com/open-policy-agent/opa/v1/rego"
|
|
14
|
+
regocompile "github.com/open-policy-agent/opa/v1/rego/compile"
|
|
15
|
+
"github.com/open-policy-agent/opa/v1/storage/inmem"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
// OpaCompileFilters partially evaluates a query with respect to the given
|
|
19
|
+
// unknowns and translates the residual into a data filter for the given
|
|
20
|
+
// target/dialect ("sql" with postgresql/mysql/sqlserver/sqlite, or "ucast"
|
|
21
|
+
// with all/prisma/linq/""). The result envelope holds
|
|
22
|
+
// {"query": <string or object>, "masks": <object or null>}.
|
|
23
|
+
//
|
|
24
|
+
//export OpaCompileFilters
|
|
25
|
+
func OpaCompileFilters(h C.ulonglong, query, inputJson, unknownsJson, target, dialect, mappingsJson, maskRule *C.char) *C.char {
|
|
26
|
+
e, err := getEngine(h)
|
|
27
|
+
if err != nil {
|
|
28
|
+
return errorJSON("invalid_handle", err.Error())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
parsedQuery, err := ast.ParseBody(C.GoString(query))
|
|
32
|
+
if err != nil {
|
|
33
|
+
return errorJSON("parse_error", err.Error())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
var unknownStrs []string
|
|
37
|
+
if err := json.Unmarshal([]byte(C.GoString(unknownsJson)), &unknownStrs); err != nil {
|
|
38
|
+
return errorJSON("invalid_json", err.Error())
|
|
39
|
+
}
|
|
40
|
+
unknowns := make([]*ast.Term, len(unknownStrs))
|
|
41
|
+
for i, u := range unknownStrs {
|
|
42
|
+
if unknowns[i], err = ast.ParseTerm(u); err != nil {
|
|
43
|
+
return errorJSON("parse_error", "unknown "+u+": "+err.Error())
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
tgt, dia := C.GoString(target), C.GoString(dialect)
|
|
48
|
+
copts := []regocompile.CompileOption{
|
|
49
|
+
regocompile.ParsedQuery(parsedQuery),
|
|
50
|
+
regocompile.ParsedUnknowns(unknowns...),
|
|
51
|
+
regocompile.Target(tgt, dia),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if s := C.GoString(mappingsJson); s != "" {
|
|
55
|
+
var mappings map[string]any
|
|
56
|
+
if err := json.Unmarshal([]byte(s), &mappings); err != nil {
|
|
57
|
+
return errorJSON("invalid_json", err.Error())
|
|
58
|
+
}
|
|
59
|
+
copts = append(copts, regocompile.Mappings(mappings))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if s := C.GoString(maskRule); s != "" {
|
|
63
|
+
ref, err := ast.ParseRef(s)
|
|
64
|
+
if err != nil {
|
|
65
|
+
return errorJSON("parse_error", "mask rule: "+err.Error())
|
|
66
|
+
}
|
|
67
|
+
copts = append(copts, regocompile.MaskRule(ref))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
e.mu.Lock()
|
|
71
|
+
ropts := []func(*rego.Rego){
|
|
72
|
+
rego.Store(inmem.NewFromObject(e.data)),
|
|
73
|
+
rego.StrictBuiltinErrors(true),
|
|
74
|
+
}
|
|
75
|
+
for path, src := range e.modules {
|
|
76
|
+
ropts = append(ropts, rego.Module(path, src))
|
|
77
|
+
}
|
|
78
|
+
for _, b := range e.builtins {
|
|
79
|
+
ropts = append(ropts, makeBuiltin(uint64(h), b))
|
|
80
|
+
}
|
|
81
|
+
e.mu.Unlock()
|
|
82
|
+
copts = append(copts, regocompile.Rego(ropts...))
|
|
83
|
+
|
|
84
|
+
prepared, err := regocompile.New(copts...).Prepare(context.Background())
|
|
85
|
+
if err != nil {
|
|
86
|
+
return errorJSON("prepare_error", err.Error())
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
evalOpts := []rego.EvalOption{}
|
|
90
|
+
if inputJson != nil {
|
|
91
|
+
if s := C.GoString(inputJson); s != "" {
|
|
92
|
+
var input any
|
|
93
|
+
if err := json.Unmarshal([]byte(s), &input); err != nil {
|
|
94
|
+
return errorJSON("invalid_json", err.Error())
|
|
95
|
+
}
|
|
96
|
+
evalOpts = append(evalOpts, rego.EvalInput(input))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
filters, err := prepared.Compile(context.Background(), evalOpts...)
|
|
101
|
+
if err != nil {
|
|
102
|
+
return errorJSON("compile_error", err.Error())
|
|
103
|
+
}
|
|
104
|
+
f := filters.For(tgt, dia)
|
|
105
|
+
return resultJSON(map[string]any{"query": f.Query, "masks": f.Masks})
|
|
106
|
+
}
|
|
@@ -11,6 +11,9 @@ require (
|
|
|
11
11
|
github.com/gobwas/glob v0.2.3 // indirect
|
|
12
12
|
github.com/goccy/go-json v0.10.6 // indirect
|
|
13
13
|
github.com/google/uuid v1.6.0 // indirect
|
|
14
|
+
github.com/huandu/go-clone v1.7.3 // indirect
|
|
15
|
+
github.com/huandu/go-sqlbuilder v1.42.1 // indirect
|
|
16
|
+
github.com/huandu/xstrings v1.4.0 // indirect
|
|
14
17
|
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
|
15
18
|
github.com/lestrrat-go/dsig v1.2.1 // indirect
|
|
16
19
|
github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
|
|
@@ -37,6 +37,15 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
|
|
37
37
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
|
38
38
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
39
39
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
40
|
+
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
|
41
|
+
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
|
42
|
+
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
|
43
|
+
github.com/huandu/go-clone v1.7.3 h1:rtQODA+ABThEn6J5LBTppJfKmZy/FwfpMUWa8d01TTQ=
|
|
44
|
+
github.com/huandu/go-clone v1.7.3/go.mod h1:ReGivhG6op3GYr+UY3lS6mxjKp7MIGTknuU5TbTVaXE=
|
|
45
|
+
github.com/huandu/go-sqlbuilder v1.42.1 h1:9DVQbKg1uFNGaVqgWMpaBNkM3Fv58p2m/+AxWkTrCYc=
|
|
46
|
+
github.com/huandu/go-sqlbuilder v1.42.1/go.mod h1:BEm32AHl29lzKDeV3HAIkzrz9cgRyumkDohHeGYYBoM=
|
|
47
|
+
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
|
48
|
+
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
|
40
49
|
github.com/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
|
|
41
50
|
github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
|
42
51
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
@@ -82,6 +91,7 @@ github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr
|
|
|
82
91
|
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
|
83
92
|
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
|
84
93
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
94
|
+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
85
95
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
86
96
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
|
87
97
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
|
@@ -128,6 +138,7 @@ google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j
|
|
|
128
138
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
129
139
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
130
140
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
141
|
+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
131
142
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
132
143
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
133
144
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "opa-golib-python-bindings"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Python bindings for the OPA (Open Policy Agent) Rego engine via a Go c-shared library"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -26,7 +26,7 @@ Repository = "https://github.com/phi1010/opa-golib-python-bindings"
|
|
|
26
26
|
Issues = "https://github.com/phi1010/opa-golib-python-bindings/issues"
|
|
27
27
|
|
|
28
28
|
[project.optional-dependencies]
|
|
29
|
-
dev = ["pytest"]
|
|
29
|
+
dev = ["pytest", "pyyaml"]
|
|
30
30
|
|
|
31
31
|
[build-system]
|
|
32
32
|
requires = ["hatchling"]
|
{opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/src/opa_bindings/_native.py
RENAMED
|
@@ -60,4 +60,16 @@ def load():
|
|
|
60
60
|
ctypes.c_int, # trace
|
|
61
61
|
]
|
|
62
62
|
|
|
63
|
+
lib.OpaCompileFilters.restype = ctypes.c_void_p
|
|
64
|
+
lib.OpaCompileFilters.argtypes = [
|
|
65
|
+
ctypes.c_uint64,
|
|
66
|
+
ctypes.c_char_p, # query
|
|
67
|
+
ctypes.c_char_p, # input JSON
|
|
68
|
+
ctypes.c_char_p, # unknowns JSON array
|
|
69
|
+
ctypes.c_char_p, # target
|
|
70
|
+
ctypes.c_char_p, # dialect
|
|
71
|
+
ctypes.c_char_p, # mappings JSON
|
|
72
|
+
ctypes.c_char_p, # mask rule ref
|
|
73
|
+
]
|
|
74
|
+
|
|
63
75
|
return lib
|
{opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/src/opa_bindings/engine.py
RENAMED
|
@@ -235,6 +235,62 @@ class OpaEngine:
|
|
|
235
235
|
raise OpaUndefinedError(f"data.{path}" if path else "data")
|
|
236
236
|
return rs[0]["expressions"][0]["value"]
|
|
237
237
|
|
|
238
|
+
def compile_filters(
|
|
239
|
+
self,
|
|
240
|
+
query: str,
|
|
241
|
+
input=None,
|
|
242
|
+
*,
|
|
243
|
+
unknowns=("input",),
|
|
244
|
+
target: str = "sql",
|
|
245
|
+
dialect: str = "postgresql",
|
|
246
|
+
mappings=None,
|
|
247
|
+
mask_rule: str | None = None,
|
|
248
|
+
):
|
|
249
|
+
"""Partially evaluate ``query`` and translate it into a data filter.
|
|
250
|
+
|
|
251
|
+
Everything under the refs in ``unknowns`` (e.g. ``"input.fruits"``)
|
|
252
|
+
is left unknown; the rest is evaluated using ``input`` and the data
|
|
253
|
+
and policies already added. The residual conditions are translated
|
|
254
|
+
for ``target``/``dialect``:
|
|
255
|
+
|
|
256
|
+
- ``target="sql"`` with dialect ``postgresql``, ``mysql``,
|
|
257
|
+
``sqlserver`` or ``sqlite``: returns a SQL WHERE clause string.
|
|
258
|
+
- ``target="ucast"`` with dialect ``all``, ``prisma``, ``linq`` or
|
|
259
|
+
``""``: returns a UCAST condition object (JSON-compatible dict).
|
|
260
|
+
|
|
261
|
+
``mappings`` optionally renames tables/columns (see the OPA docs on
|
|
262
|
+
Compile API mappings); ``mask_rule`` names a rule (e.g.
|
|
263
|
+
``"data.filters.masks"``) evaluated to produce column masks.
|
|
264
|
+
|
|
265
|
+
Returns ``{"query": <str or dict or None>, "masks": <dict or None>}``.
|
|
266
|
+
A query of ``None`` means the policy can never be satisfied; an empty
|
|
267
|
+
query means it is always satisfied. Raises OpaError with code
|
|
268
|
+
``compile_error`` if the residual policy cannot be expressed as a
|
|
269
|
+
filter for the chosen target.
|
|
270
|
+
|
|
271
|
+
Unknown refs are limited to ``input.<table>.<column>`` — exactly two
|
|
272
|
+
segments after ``input``, for every target/dialect, wherever the
|
|
273
|
+
declared unknown boundary sits (``mappings`` cannot deepen this):
|
|
274
|
+
``unknowns=["input.item"]`` permits only ``input.item.<column>``,
|
|
275
|
+
the bare ``unknowns=["input"]`` permits ``input.<table>.<column>``.
|
|
276
|
+
Nested documents like ``input.item.attrs.price.value`` are rejected
|
|
277
|
+
and must be flattened into columns; a dynamic column picked from
|
|
278
|
+
known values (``input.attr[sprintf("value_%s", [input.locale])]``)
|
|
279
|
+
is fine, as it resolves during partial evaluation.
|
|
280
|
+
"""
|
|
281
|
+
self._check_open()
|
|
282
|
+
envelope = self._call(
|
|
283
|
+
self._lib.OpaCompileFilters,
|
|
284
|
+
query.encode(),
|
|
285
|
+
_encode_input(input),
|
|
286
|
+
json.dumps(list(unknowns)).encode(),
|
|
287
|
+
target.encode(),
|
|
288
|
+
dialect.encode(),
|
|
289
|
+
b"" if mappings is None else json.dumps(mappings).encode(),
|
|
290
|
+
(mask_rule or "").encode(),
|
|
291
|
+
)
|
|
292
|
+
return envelope.get("result")
|
|
293
|
+
|
|
238
294
|
|
|
239
295
|
def _encode_input(input):
|
|
240
296
|
if input is None:
|
|
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
|
{opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.3.0}/src/opa_bindings/__init__.py
RENAMED
|
File without changes
|