opa-python-client 1.3.7__tar.gz → 2.0.1__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.
@@ -0,0 +1,534 @@
1
+ Metadata-Version: 2.1
2
+ Name: opa-python-client
3
+ Version: 2.0.1
4
+ Summary: Client for connection to the OPA service
5
+ Home-page: https://github.com/Turall/OPA-python-client
6
+ License: MIT
7
+ Author: Tural Muradov
8
+ Author-email: tural.muradoov@gmail.com
9
+ Requires-Python: >=3.9,<4.0
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Requires-Dist: aiofiles (>=24.1.0,<25.0.0)
22
+ Requires-Dist: aiohttp[speedups] (>=3.10.9,<4.0.0)
23
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
24
+ Project-URL: Repository, https://github.com/Turall/OPA-python-client
25
+ Description-Content-Type: text/markdown
26
+
27
+
28
+ # OpaClient - Open Policy Agent Python Client
29
+ [![MIT licensed](https://img.shields.io/github/license/Turall/OPA-python-client)](https://raw.githubusercontent.com/Turall/OPA-python-client/master/LICENSE)
30
+ [![GitHub stars](https://img.shields.io/github/stars/Turall/OPA-python-client.svg)](https://github.com/Turall/OPA-python-client/stargazers)
31
+ [![GitHub forks](https://img.shields.io/github/forks/Turall/OPA-python-client.svg)](https://github.com/Turall/OPA-python-client/network)
32
+ [![GitHub issues](https://img.shields.io/github/issues-raw/Turall/OPA-python-client)](https://github.com/Turall/OPA-python-client/issues)
33
+ [![Downloads](https://pepy.tech/badge/opa-python-client)](https://pepy.tech/project/opa-python-client)
34
+
35
+ OpaClient is a Python client library designed to interact with the [Open Policy Agent (OPA)](https://www.openpolicyagent.org/). It supports both **synchronous** and **asynchronous** requests, making it easy to manage policies, data, and evaluate rules in OPA servers.
36
+
37
+ ## Features
38
+
39
+ - **Manage Policies**: Create, update, retrieve, and delete policies.
40
+ - **Manage Data**: Create, update, retrieve, and delete data in OPA.
41
+ - **Evaluate Policies**: Use input data to evaluate policies and return decisions.
42
+ - **Synchronous & Asynchronous**: Choose between sync or async operations to suit your application.
43
+ - **SSL/TLS Support**: Communicate securely with SSL/TLS, including client certificates.
44
+ - **Customizable**: Use custom headers, timeouts, and other configurations.
45
+
46
+ ## Installation
47
+
48
+ You can install the OpaClient package via `pip`:
49
+
50
+ ```bash
51
+ pip install opa-python-client
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ ### Synchronous Client Example
57
+
58
+ ```python
59
+ from opa_client.opa import OpaClient
60
+
61
+ # Initialize the OPA client
62
+ client = OpaClient(host='localhost', port=8181)
63
+
64
+ # Check the OPA server connection
65
+ try:
66
+ print(client.check_connection()) # True
67
+ finally:
68
+ client.close_connection()
69
+ ```
70
+ or with client factory
71
+
72
+ ```python
73
+ from opa_client import create_opa_client
74
+
75
+ client = create_opa_client(host="localhost", port=8181)
76
+
77
+ ```
78
+
79
+ Check OPA healthy. If you want check bundels or plugins, add query params for this.
80
+
81
+ ```python
82
+ from opa_client.opa import OpaClient
83
+
84
+ client = OpaClient()
85
+
86
+ print(client.check_health()) # response is True or False
87
+ print(client.check_health({"bundle": True})) # response is True or False
88
+ # If your diagnostic url different than default url, you can provide it.
89
+ print(client.check_health(diagnostic_url="http://localhost:8282/health")) # response is True or False
90
+ print(client.check_health(query={"bundle": True}, diagnostic_url="http://localhost:8282/health")) # response is True or False
91
+ ```
92
+
93
+ ### Asynchronous Client Example
94
+
95
+ ```python
96
+ import asyncio
97
+ from opa_client.opa_async import AsyncOpaClient
98
+
99
+ async def main():
100
+ async with AsyncOpaClient(host='localhost', port=8181) as client:
101
+ result = await client.check_connection()
102
+ print(result)
103
+
104
+ # Run the async main function
105
+ asyncio.run(main())
106
+ ```
107
+ or with clien factory
108
+
109
+ ```python
110
+ from opa_client import create_opa_client
111
+
112
+ client = create_opa_client(async_mode=True,host="localhost", port=8181)
113
+
114
+ ```
115
+
116
+ ## Secure Connection with SSL/TLS
117
+
118
+ You can use OpaClient with secure SSL/TLS connections, including mutual TLS (mTLS), by providing a client certificate and key.
119
+
120
+ ### Synchronous Client with SSL/TLS
121
+
122
+ ```python
123
+ from opa_client.opa import OpaClient
124
+
125
+ # Path to your certificate and private key
126
+ cert_path = '/path/to/client_cert.pem'
127
+ key_path = '/path/to/client_key.pem'
128
+
129
+ # Initialize the OPA client with SSL/TLS
130
+ client = OpaClient(
131
+ host='your-opa-server.com',
132
+ port=443, # Typically for HTTPS
133
+ ssl=True,
134
+ cert=(cert_path, key_path) # Provide the certificate and key as a tuple
135
+ )
136
+
137
+ # Check the OPA server connection
138
+ try:
139
+ result = client.check_connection()
140
+ print(result)
141
+ finally:
142
+ client.close_connection()
143
+ ```
144
+
145
+ ### Asynchronous Client with SSL/TLS
146
+
147
+ ```python
148
+ import asyncio
149
+ from opa_client.opa_async import AsyncOpaClient
150
+
151
+ # Path to your certificate and private key
152
+ cert_path = '/path/to/client_cert.pem'
153
+ key_path = '/path/to/client_key.pem'
154
+
155
+ async def main():
156
+ # Initialize the OPA client with SSL/TLS
157
+ async with AsyncOpaClient(
158
+ host='your-opa-server.com',
159
+ port=443, # Typically for HTTPS
160
+ ssl=True,
161
+ cert=(cert_path, key_path) # Provide the certificate and key as a tuple
162
+ ) as client:
163
+ # Check the OPA server connection
164
+ result = await client.check_connection()
165
+ print(result)
166
+
167
+ # Run the async main function
168
+ asyncio.run(main())
169
+ ```
170
+
171
+ ## Usage
172
+
173
+ ### Policy Management
174
+
175
+ #### Create or Update a Policy
176
+
177
+ You can create or update a policy using the following syntax:
178
+
179
+ - **Synchronous**:
180
+
181
+ ```python
182
+ policy_name = 'example_policy'
183
+ policy_content = '''
184
+ package example
185
+
186
+ default allow = false
187
+
188
+ allow {
189
+ input.user == "admin"
190
+ }
191
+ '''
192
+
193
+ client.update_policy_from_string(policy_content, policy_name)
194
+ ```
195
+
196
+ - **Asynchronous**:
197
+
198
+ ```python
199
+ await client.update_policy_from_string(policy_content, policy_name)
200
+ ```
201
+
202
+ Or from url:
203
+
204
+ - **Synchronous**:
205
+
206
+ ```python
207
+ policy_name = 'example_policy'
208
+
209
+ client.update_policy_from_url("http://opapolicyurlexample.test/example.rego", policy_name)
210
+
211
+ ```
212
+
213
+ - **Asynchronous**:
214
+
215
+ ```python
216
+ await client.update_policy_from_url("http://opapolicyurlexample.test/example.rego", policy_name)
217
+ ```
218
+
219
+ Update policy from rego file
220
+
221
+ ```python
222
+ client.update_opa_policy_fromfile("/your/path/filename.rego", endpoint="fromfile") # response is True
223
+
224
+ client.get_policies_list()
225
+ ```
226
+
227
+ - **Asynchronous**:
228
+ ```python
229
+ await client.update_opa_policy_fromfile("/your/path/filename.rego", endpoint="fromfile") # response is True
230
+
231
+ await client.get_policies_list()
232
+ ```
233
+
234
+ #### Retrieve a Policy
235
+
236
+ After creating a policy, you can retrieve it:
237
+
238
+ - **Synchronous**:
239
+
240
+ ```python
241
+ policy = client.get_policy('example_policy')
242
+ print(policy)
243
+ # or
244
+ policies = client.get_policies_list()
245
+ print(policies)
246
+ ```
247
+
248
+ - **Asynchronous**:
249
+
250
+ ```python
251
+ policy = await client.get_policy('example_policy')
252
+ print(policy)
253
+
254
+ # or
255
+ policies = await client.get_policies_list()
256
+ print(policies)
257
+ ```
258
+
259
+ Save policy to file from OPA service
260
+
261
+ ```python
262
+ client.policy_to_file(policy_name="example_policy",path="/your/path",filename="example.rego")
263
+
264
+ ```
265
+
266
+ - **Asynchronous**:
267
+
268
+ ```python
269
+
270
+ await client.policy_to_file(policy_name="example_policy",path="/your/path",filename="example.rego")
271
+
272
+ ```
273
+
274
+ Information about policy path and rules
275
+
276
+ ```python
277
+
278
+ print(client.get_policies_info())
279
+ #{'example_policy': {'path': 'http://localhost:8181/v1/data/example', 'rules': ['http://localhost:8181/v1/data/example/allow']}}
280
+
281
+ ```
282
+ - **Asynchronous**:
283
+
284
+
285
+ ```python
286
+
287
+ print(await client.get_policies_info())
288
+ #{'example_policy': {'path': 'http://localhost:8181/v1/data/example', 'rules': ['http://localhost:8181/v1/data/example/allow']}}
289
+
290
+ ```
291
+
292
+ #### Delete a Policy
293
+
294
+ You can delete a policy by name:
295
+
296
+ - **Synchronous**:
297
+
298
+ ```python
299
+ client.delete_policy('example_policy')
300
+ ```
301
+
302
+ - **Asynchronous**:
303
+
304
+ ```python
305
+ await client.delete_policy('example_policy')
306
+ ```
307
+
308
+ ### Data Management
309
+
310
+ #### Create or Update Data
311
+
312
+ You can upload arbitrary data to OPA:
313
+
314
+ - **Synchronous**:
315
+
316
+ ```python
317
+ data_name = 'users'
318
+ data_content = {
319
+ "users": [
320
+ {"name": "alice", "role": "admin"},
321
+ {"name": "bob", "role": "user"}
322
+ ]
323
+ }
324
+
325
+ client.update_or_create_data(data_content, data_name)
326
+ ```
327
+
328
+ - **Asynchronous**:
329
+
330
+ ```python
331
+ await client.update_or_create_data(data_content, data_name)
332
+ ```
333
+
334
+ #### Retrieve Data
335
+
336
+ You can fetch the data stored in OPA:
337
+
338
+ - **Synchronous**:
339
+
340
+ ```python
341
+ data = client.get_data('users')
342
+ print(data)
343
+ # You can use query params for additional info
344
+ # provenance - If parameter is true, response will include build/version info in addition to the result.
345
+ # metrics - Return query performance metrics in addition to result
346
+ data = client.get_data('users',query_params={"provenance": True})
347
+ print(data) # {'provenance': {'version': '0.68.0', 'build_commit': 'db53d77c482676fadd53bc67a10cf75b3d0ce00b', 'build_timestamp': '2024-08-29T15:23:19Z', 'build_hostname': '3aae2b82a15f'}, 'result': {'users': [{'name': 'alice', 'role': 'admin'}, {'name': 'bob', 'role': 'user'}]}}
348
+
349
+
350
+ data = client.get_data('users',query_params={"metrics": True})
351
+ print(data) # {'metrics': {'counter_server_query_cache_hit': 0, 'timer_rego_external_resolve_ns': 7875, 'timer_rego_input_parse_ns': 875, 'timer_rego_query_compile_ns': 501083, 'timer_rego_query_eval_ns': 50250, 'timer_rego_query_parse_ns': 199917, 'timer_server_handler_ns': 1031291}, 'result': {'users': [{'name': 'alice', 'role': 'admin'}, {'name': 'bob', 'role': 'user'}]}}
352
+
353
+
354
+ ```
355
+
356
+ - **Asynchronous**:
357
+
358
+ ```python
359
+ data = await client.get_data('users')
360
+ print(data)
361
+ ```
362
+
363
+ #### Delete Data
364
+
365
+ To delete data from OPA:
366
+
367
+ - **Synchronous**:
368
+
369
+ ```python
370
+ client.delete_data('users')
371
+ ```
372
+
373
+ - **Asynchronous**:
374
+
375
+ ```python
376
+ await client.delete_data('users')
377
+ ```
378
+
379
+ ### Policy Evaluation
380
+
381
+ #### Check Permission (Policy Evaluation)
382
+
383
+ You can evaluate policies with input data using `check_permission`.
384
+
385
+ - **Synchronous**:
386
+
387
+ ```python
388
+ input_data = {"user": "admin"}
389
+ policy_name = 'example_policy'
390
+ rule_name = 'allow'
391
+
392
+ result = client.check_permission(input_data, policy_name, rule_name)
393
+ print(result)
394
+ ```
395
+
396
+ - **Asynchronous**:
397
+
398
+ ```python
399
+ input_data = {"user": "admin"}
400
+ policy_name = 'example_policy'
401
+ rule_name = 'allow'
402
+
403
+ result = await client.check_permission(input_data, policy_name, rule_name)
404
+ print(result)
405
+ ```
406
+
407
+ Queries a package rule with the given input data
408
+
409
+ ```python
410
+
411
+ rego = """
412
+ package play
413
+
414
+ default hello = false
415
+
416
+ hello {
417
+ m := input.message
418
+ m == "world"
419
+ }
420
+ """
421
+
422
+ check_data = {"message": "world"}
423
+
424
+ client.update_policy_from_string(rego, "test")
425
+ print(client.query_rule(input_data=check_data, package_path="play", rule_name="hello")) # {'result': True}
426
+
427
+ ```
428
+
429
+ - **Asynchronous**:
430
+
431
+ ```python
432
+
433
+ rego = """
434
+ package play
435
+
436
+ default hello = false
437
+
438
+ hello {
439
+ m := input.message
440
+ m == "world"
441
+ }
442
+ """
443
+
444
+ check_data = {"message": "world"}
445
+
446
+ await client.update_policy_from_string(rego, "test")
447
+ print(await client.query_rule(input_data=check_data, package_path="play", rule_name="hello")) # {'result': True}
448
+
449
+ ```
450
+
451
+ ### Ad-hoc Queries
452
+
453
+ Execute ad-hoc queries directly:
454
+
455
+ - **Synchronous**:
456
+
457
+ ```python
458
+ data = {
459
+ "user_roles": {
460
+ "alice": [
461
+ "admin"
462
+ ],
463
+ "bob": [
464
+ "employee",
465
+ "billing"
466
+ ],
467
+ "eve": [
468
+ "customer"
469
+ ]
470
+ }
471
+ }
472
+ input_data = {"user": "admin"}
473
+ client.update_or_create_data(data, "userinfo")
474
+
475
+ result = client.ad_hoc_query(query="data.userinfo.user_roles[name]")
476
+ print(result) # {'result': [{'name': 'alice'}, {'name': 'bob'}, {'name': 'eve'}]}
477
+ ```
478
+
479
+ - **Asynchronous**:
480
+
481
+ ```python
482
+ data = {
483
+ "user_roles": {
484
+ "alice": [
485
+ "admin"
486
+ ],
487
+ "bob": [
488
+ "employee",
489
+ "billing"
490
+ ],
491
+ "eve": [
492
+ "customer"
493
+ ]
494
+ }
495
+ }
496
+ input_data = {"user": "admin"}
497
+ await client.update_or_create_data(data, "userinfo")
498
+
499
+ result = await client.ad_hoc_query(query="data.userinfo.user_roles[name]")
500
+ print(result) # {'result': [{'name': 'alice'}, {'name': 'bob'}, {'name': 'eve'}]}
501
+ ```
502
+
503
+ ## API Reference
504
+
505
+ ### Synchronous Client (OpaClient)
506
+
507
+ - `check_connection()`: Verify connection to OPA server.
508
+ - `get_policies_list()`: Get a list of all policies.
509
+ - `get_policies_info()`: Returns information about each policy, including policy path and policy rules.
510
+ - `get_policy(policy_name)`: Fetch a specific policy.
511
+ - `policy_to_file(policy_name)`: Save an OPA policy to a file..
512
+ - `update_policy_from_string(policy_content, policy_name)`: Upload or update a policy using its string content.
513
+ - `update_policy_from_url(url,endpoint)`: Update OPA policy by fetching it from a URL.
514
+ - `update_policy_from_file(filepath,endpoint)`: Update OPA policy using a policy file.
515
+ - `delete_policy(policy_name)`: Delete a specific policy.
516
+ - `update_or_create_data(data_content, data_name)`: Create or update data in OPA.
517
+ - `get_data(data_name)`: Retrieve data from OPA.
518
+ - `delete_data(data_name)`: Delete data from OPA.
519
+ - `check_permission(input_data, policy_name, rule_name)`: Evaluate a policy using input data.
520
+ - `query_rule(input_data, package_path, rule_name)`: Query a specific rule in a package.
521
+ - `ad_hoc_query(query, input_data)`: Run an ad-hoc query.
522
+
523
+ ### Asynchronous Client (AsyncOpaClient)
524
+
525
+ Same as the synchronous client, but all methods are asynchronous and must be awaited.
526
+
527
+ ## Contributing
528
+
529
+ Contributions are welcome! Feel free to open issues, fork the repo, and submit pull requests.
530
+
531
+ ## License
532
+
533
+ This project is licensed under the MIT License.
534
+