opa-golib-python-bindings 0.2.0__tar.gz → 0.4.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.
Files changed (18) hide show
  1. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/PKG-INFO +47 -1
  2. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/README.md +45 -0
  3. opa_golib_python_bindings-0.4.0/go/compile.go +106 -0
  4. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/go.mod +12 -9
  5. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/go.sum +51 -43
  6. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/pyproject.toml +2 -2
  7. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/src/opa_bindings/_native.py +12 -0
  8. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/src/opa_bindings/engine.py +56 -0
  9. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/.gitignore +0 -0
  10. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/LICENSE +0 -0
  11. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/Makefile +0 -0
  12. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/bridge.go +0 -0
  13. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/bridge_call.c +0 -0
  14. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/builtins.go +0 -0
  15. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/merge.go +0 -0
  16. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/go/trace.go +0 -0
  17. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.0}/hatch_build.py +0 -0
  18. {opa_golib_python_bindings-0.2.0 → opa_golib_python_bindings-0.4.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.2.0
3
+ Version: 0.4.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
+ }
@@ -1,8 +1,8 @@
1
1
  module opabridge
2
2
 
3
- go 1.26
3
+ go 1.26.0
4
4
 
5
- require github.com/open-policy-agent/opa v1.19.1
5
+ require github.com/open-policy-agent/opa v1.20.2
6
6
 
7
7
  require (
8
8
  github.com/agnivade/levenshtein v1.2.1 // indirect
@@ -11,16 +11,19 @@ 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
- github.com/lestrrat-go/dsig v1.2.1 // indirect
18
+ github.com/lestrrat-go/dsig v1.3.0 // indirect
16
19
  github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect
17
20
  github.com/lestrrat-go/httpcc v1.0.1 // indirect
18
- github.com/lestrrat-go/httprc/v3 v3.0.5 // indirect
19
- github.com/lestrrat-go/jwx/v3 v3.1.1 // indirect
21
+ github.com/lestrrat-go/httprc/v3 v3.0.6 // indirect
22
+ github.com/lestrrat-go/jwx/v3 v3.2.0 // indirect
20
23
  github.com/lestrrat-go/option/v2 v2.0.0 // indirect
21
24
  github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect
22
25
  github.com/segmentio/asm v1.2.1 // indirect
23
- github.com/sirupsen/logrus v1.9.4 // indirect
26
+ github.com/sirupsen/logrus v1.10.2 // indirect
24
27
  github.com/tchap/go-patricia/v2 v2.3.3 // indirect
25
28
  github.com/valyala/fastjson v1.6.10 // indirect
26
29
  github.com/vektah/gqlparser/v2 v2.5.36 // indirect
@@ -28,10 +31,10 @@ require (
28
31
  github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
29
32
  github.com/yashtewari/glob-intersection v0.2.0 // indirect
30
33
  go.yaml.in/yaml/v2 v2.4.4 // indirect
31
- go.yaml.in/yaml/v3 v3.0.4 // indirect
32
- golang.org/x/crypto v0.53.0 // indirect
34
+ go.yaml.in/yaml/v3 v3.0.5 // indirect
35
+ golang.org/x/crypto v0.55.0 // indirect
33
36
  golang.org/x/sync v0.22.0 // indirect
34
37
  golang.org/x/sys v0.47.0 // indirect
35
- google.golang.org/protobuf v1.36.11 // indirect
38
+ google.golang.org/protobuf v1.36.12 // indirect
36
39
  sigs.k8s.io/yaml v1.6.0 // indirect
37
40
  )
@@ -11,8 +11,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
11
11
  github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12
12
  github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 h1:5RVFMOWjMyRy8cARdy79nAmgYw3hK/4HUq48LQ6Wwqo=
13
13
  github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
14
- github.com/dgraph-io/badger/v4 v4.9.4 h1:bcw+waCpzRZ2nmcSPbnPvDVhiEsn98TKmvnAhK7r7LM=
15
- github.com/dgraph-io/badger/v4 v4.9.4/go.mod h1:nJjaJTUOSsQEBhsq209FmwCvMJzEA3e74RjZw6V2pQI=
14
+ github.com/dgraph-io/badger/v4 v4.9.6 h1:IQqMPVGLNCQr1b4Mu8lHkYm/xyqFRsyKaFEtyLi9CCQ=
15
+ github.com/dgraph-io/badger/v4 v4.9.6/go.mod h1:Xa9dAupjbwAacupWFCpa6YEn9E1PjBXkfZYr2I/8aWg=
16
16
  github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
17
17
  github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
18
18
  github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
@@ -37,54 +37,63 @@ 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/klauspost/compress v1.19.0 h1:sXLILfc9jV2QYWkzFOPWStmcUVH2RHEB1JCdY2oVvCQ=
41
- github.com/klauspost/compress v1.19.0/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
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=
49
+ github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk=
50
+ github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
42
51
  github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
43
52
  github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
44
53
  github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
45
54
  github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
46
55
  github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
47
56
  github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
48
- github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc=
49
- github.com/lestrrat-go/dsig v1.2.1/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc=
57
+ github.com/lestrrat-go/dsig v1.3.0 h1:phjMOCXvYzhuIgn7Voe2rex8z166vGfxRxmqM25P9/Q=
58
+ github.com/lestrrat-go/dsig v1.3.0/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc=
50
59
  github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY=
51
60
  github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU=
52
61
  github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
53
62
  github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
54
- github.com/lestrrat-go/httprc/v3 v3.0.5 h1:S+Mb4L2I+bM6JGTibLmxExhyTOqnXjqx+zi9MoXw/TM=
55
- github.com/lestrrat-go/httprc/v3 v3.0.5/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
56
- github.com/lestrrat-go/jwx/v3 v3.1.1 h1:yd9AdPmZ4INnQ7k42IrzXYpnEG803+SrQ6hdMvzHJzw=
57
- github.com/lestrrat-go/jwx/v3 v3.1.1/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU=
63
+ github.com/lestrrat-go/httprc/v3 v3.0.6 h1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI=
64
+ github.com/lestrrat-go/httprc/v3 v3.0.6/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0=
65
+ github.com/lestrrat-go/jwx/v3 v3.2.0 h1:Jb3zBASTSZXz7gzzSAfYqxXF8KejvKC4xWoePLQqXCA=
66
+ github.com/lestrrat-go/jwx/v3 v3.2.0/go.mod h1:38vQ8iWKq3qRSbilbzvzdQPuywhowwuR03lhkYskyrw=
58
67
  github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss=
59
68
  github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg=
60
69
  github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
61
70
  github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
62
71
  github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
63
72
  github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
64
- github.com/open-policy-agent/opa v1.19.1 h1:aB1nOncChnTbQurjRQVJnjTJxditt8VqszlbaM3GGKU=
65
- github.com/open-policy-agent/opa v1.19.1/go.mod h1:pb6Y6klyf7X7X8uXNDflruA9dQC2gMqWROXI5w/kvv0=
66
- github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
73
+ github.com/open-policy-agent/opa v1.20.2 h1:dlX5VEGA5M62W8+X1nRPEaQIh/CMRn5FcD1mebpKWKg=
74
+ github.com/open-policy-agent/opa v1.20.2/go.mod h1:yYKTQAlkbefoXSYChT4ek8gc8WCtScL7L7OSZxFVe0s=
67
75
  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
68
- github.com/prometheus/client_golang v1.24.0 h1:5XStIklKuAtJSNpdD3s8XJj/Yv78IQmE1kbNk87JrAI=
69
- github.com/prometheus/client_golang v1.24.0/go.mod h1:QcsNdotprC2nS4BTM2ucbcqxd2CeXTEa9jW7zHO9iDE=
76
+ github.com/prometheus/client_golang v1.24.1 h1:JnJkREXzWxUdCuPFpIWZiPispT9xVV59uiuyR2bPlnU=
77
+ github.com/prometheus/client_golang v1.24.1/go.mod h1:F+oSRECHg4sse5ucfYpYDeIv/hu68Zo0uoHKetWnzcE=
70
78
  github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
71
79
  github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
72
- github.com/prometheus/common v0.70.0 h1:bcpru3tWPVnxGnETLgOV5jbp/JRXgYEyv65CuBLAMMI=
73
- github.com/prometheus/common v0.70.0/go.mod h1:S/SFasQmgGiYH6C81LKCtYa8QACgthGg5zxL2udV7SY=
80
+ github.com/prometheus/common v0.70.1 h1:1HvjP4D5oL3t8RsPlwxA9onvvStjtIHYE5XuuwOi/PY=
81
+ github.com/prometheus/common v0.70.1/go.mod h1:VdFUQDMZK3VLkurFUVhia6uys/0suUp86TJz5qbJRhc=
74
82
  github.com/prometheus/procfs v0.21.1 h1:GljZCt+zSTS+NZq88cyQ1LjZ+RCHp3uVuabBWA5+OJI=
75
83
  github.com/prometheus/procfs v0.21.1/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY=
76
84
  github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg=
77
85
  github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
78
- github.com/rogpeppe/go-internal v1.15.0 h1:D0RCU5rMAp+SpgkiNdrjfJ+LX4J1M32V2NeCY7EJ6hc=
79
- github.com/rogpeppe/go-internal v1.15.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
86
+ github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
87
+ github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
80
88
  github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
81
89
  github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
82
- github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
83
- github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
90
+ github.com/sirupsen/logrus v1.10.2 h1:G2SED73/qrAu6YwbdxOD6peLkCBI3z7L+ykJFTXJBBo=
91
+ github.com/sirupsen/logrus v1.10.2/go.mod h1:SLEg8TqYulVKKfIGHldVp2K2aYz2DKSVBq4g/H5bR7Q=
84
92
  github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
93
+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
85
94
  github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
86
- github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
87
- github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
95
+ github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
96
+ github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
88
97
  github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc=
89
98
  github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k=
90
99
  github.com/tetratelabs/wazero v1.12.0 h1:DuWcpNu/FzgEXgGBDp8J1Spc+CWOvvtvVyjKlaZopYU=
@@ -101,35 +110,34 @@ github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBe
101
110
  github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok=
102
111
  go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
103
112
  go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
104
- go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
105
- go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
106
- go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
107
- go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
108
- go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
109
- go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
113
+ go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc=
114
+ go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE=
115
+ go.opentelemetry.io/otel/metric v1.46.0 h1:yBnkXvgV7AXFILZc5K6IZe/CBFF3OS7BJ8ov6/lj0K8=
116
+ go.opentelemetry.io/otel/metric v1.46.0/go.mod h1:iPmdWqifKUdzziPkvvzIJXITl56fQx2mGM/DHLB3/2o=
117
+ go.opentelemetry.io/otel/trace v1.46.0 h1:OULy7ccdJnZtJ0UDYFOIGaCmiWzJ8Vi2G/Rsu60qs1c=
118
+ go.opentelemetry.io/otel/trace v1.46.0/go.mod h1:J7GAXweO77XSFkB/rmAqk9D6ihszhFjLU+d9WuUxDLI=
110
119
  go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
111
120
  go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
112
- go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
113
- go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
114
- golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
115
- golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
116
- golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
117
- golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
118
- golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
119
- golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
121
+ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
122
+ go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
123
+ golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
124
+ golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
125
+ golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
126
+ golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
127
+ golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
128
+ golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
120
129
  golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
121
130
  golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
122
131
  golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
123
132
  golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
124
- golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
125
- golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
126
- google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
127
- google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
133
+ golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
134
+ golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
135
+ google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
136
+ google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
128
137
  gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
129
138
  gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
130
139
  gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
140
+ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
131
141
  gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
132
- gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
133
- gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
134
142
  sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
135
143
  sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "opa-golib-python-bindings"
3
- version = "0.2.0"
3
+ version = "0.4.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"]
@@ -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
@@ -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: