chill-sharp-py-client 1.1.9__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,567 @@
1
+ Metadata-Version: 2.4
2
+ Name: chill-sharp-py-client
3
+ Version: 1.1.9
4
+ Summary: Python client for generic ChillSharp services
5
+ Author: Andrea Piovesan
6
+ License: AGPL-3.0-or-later
7
+ Keywords: chillsharp,client,rest,ef-core,python
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3 :: Only
10
+ Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: requests>=2.31.0
15
+
16
+ # chill-sharp-py-client
17
+
18
+ Python client for a generic ChillSharp service.
19
+
20
+ This package targets the standard ChillSharp HTTP surface:
21
+
22
+ - core Chill API at `/api/chill`
23
+ - schema API at `/api/chill-schema`
24
+ - auth API at `/api/chill-auth`
25
+ - i18n API at `/api/chill-i18n`
26
+
27
+ It is intentionally lightweight. Payloads are plain Python dictionaries so the client can work against arbitrary ChillSharp models without code generation.
28
+
29
+ ## Install
30
+
31
+ From the repository root:
32
+
33
+ ```bash
34
+ pip install -e extra/chill-sharp-py-client
35
+ ```
36
+
37
+ Or inside the package folder:
38
+
39
+ ```bash
40
+ cd extra/chill-sharp-py-client
41
+ pip install .
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```python
47
+ from chillsharp_py_client import ChillSharpClient
48
+
49
+ client = ChillSharpClient("http://localhost:5000/api/chill", culture_name="it-IT")
50
+
51
+ created = client.create({
52
+ "ChillType": "Model.Post",
53
+ "Guid": "00000000-0000-0000-0000-000000000001",
54
+ "Properties": {
55
+ "Title": "Hello",
56
+ "Author": "Ada Lovelace",
57
+ },
58
+ })
59
+
60
+ found = client.find({
61
+ "ChillType": "Model.Post",
62
+ "Guid": created["Guid"],
63
+ })
64
+ ```
65
+
66
+ ## Construction Modes
67
+
68
+ ### Anonymous or externally authenticated
69
+
70
+ ```python
71
+ client = ChillSharpClient("http://localhost:5000/api/chill", culture_name="it-IT")
72
+ ```
73
+
74
+ ### With an existing access token
75
+
76
+ ```python
77
+ client = ChillSharpClient(
78
+ "http://localhost:5000/api/chill",
79
+ access_token="your-jwt-token",
80
+ culture_name="it-IT",
81
+ )
82
+ ```
83
+
84
+ ### With username and password
85
+
86
+ ```python
87
+ client = ChillSharpClient(
88
+ "http://localhost:5000/api/chill",
89
+ username="root",
90
+ password="Pass123$",
91
+ culture_name="it-IT",
92
+ )
93
+ ```
94
+
95
+ If the service supports ChillSharp auth endpoints, the client can log in and refresh tokens automatically.
96
+
97
+ ## Core ChillSharp Operations
98
+
99
+ Query payloads can include an `Ordering` object with `PropertyName` and `Direction`.
100
+ If you omit `Ordering`, the backend defaults to `Position`. Entity payloads also include `Position`, with default value `0`.
101
+
102
+ ### Query
103
+
104
+ Use `query()` when `ChillType` points to a concrete query type such as `Query.PostQuery`.
105
+
106
+ ```python
107
+ result = client.query({
108
+ "ChillType": "Query.PostQuery",
109
+ "Properties": {
110
+ "Title": "Hello"
111
+ },
112
+ "Ordering": {
113
+ "PropertyName": "Position",
114
+ "Direction": "ASC",
115
+ },
116
+ "ResultProperties": [
117
+ {"Name": "Guid"},
118
+ {"Name": "Title"},
119
+ {"Name": "Author"},
120
+ ],
121
+ })
122
+ ```
123
+
124
+ If `Ordering.PropertyName` points to a Chill entity reference such as `Blog`, the backend orders by `Blog.Label`.
125
+
126
+ ### Lookup
127
+
128
+ Use `lookup()` when `ChillType` points to an entity type and you only need generic full-text search.
129
+
130
+ ```python
131
+ result = client.lookup({
132
+ "ChillType": "Model.Post",
133
+ "Properties": {
134
+ "FullTextSearch": "Ada Lovelace"
135
+ },
136
+ "Ordering": {
137
+ "PropertyName": "Blog",
138
+ "Direction": "ASC",
139
+ },
140
+ "ResultProperties": [
141
+ {"Name": "Guid"},
142
+ {"Name": "Title"},
143
+ {"Name": "Author"},
144
+ ],
145
+ })
146
+ ```
147
+
148
+ ### Find
149
+
150
+ ```python
151
+ entity = client.find({
152
+ "ChillType": "Model.Post",
153
+ "Guid": "f2d5d5e3-0a1f-4d15-9396-2ab5f6c4ff11",
154
+ })
155
+ ```
156
+
157
+ ### Create
158
+
159
+ ```python
160
+ entity = client.create({
161
+ "ChillType": "Model.Post",
162
+ "Guid": "f2d5d5e3-0a1f-4d15-9396-2ab5f6c4ff11",
163
+ "Position": 10,
164
+ "Properties": {
165
+ "Title": "New title",
166
+ "Author": "Grace Hopper",
167
+ },
168
+ })
169
+ ```
170
+
171
+ ### Update
172
+
173
+ ```python
174
+ updated = client.update({
175
+ "ChillType": "Model.Post",
176
+ "Guid": "f2d5d5e3-0a1f-4d15-9396-2ab5f6c4ff11",
177
+ "Position": 20,
178
+ "Properties": {
179
+ "Title": "Updated title",
180
+ },
181
+ })
182
+ ```
183
+
184
+ ### Delete
185
+
186
+ ```python
187
+ client.delete({
188
+ "ChillType": "Model.Post",
189
+ "Guid": "f2d5d5e3-0a1f-4d15-9396-2ab5f6c4ff11",
190
+ })
191
+ ```
192
+
193
+ ### Attachments
194
+
195
+ Use the attachment helpers when the host enables `ChillSharp.Attachment`.
196
+
197
+ ```python
198
+ post = {
199
+ "ChillType": "Model.Post",
200
+ "Guid": "f2d5d5e3-0a1f-4d15-9396-2ab5f6c4ff11",
201
+ }
202
+
203
+ uploaded = client.upload_attachment(
204
+ post,
205
+ {
206
+ "fileName": "contract.txt",
207
+ "content": b"hello attachment",
208
+ "contentType": "text/plain",
209
+ },
210
+ title="Contract",
211
+ description="Signed draft",
212
+ is_public=False,
213
+ )
214
+
215
+ attachments = client.get_attachments(post)
216
+ file_bytes = client.download_attachment(uploaded[0])
217
+ ```
218
+
219
+ ### Chunk
220
+
221
+ Use `chunk()` when several operations should be sent in one HTTP request.
222
+ The operations are executed in `Index` order when you provide it. For write-heavy batches, set `Index` explicitly.
223
+
224
+ ```python
225
+ operations = client.chunk([
226
+ {
227
+ "Index": 0,
228
+ "Verb": "create",
229
+ "Entity": {
230
+ "ChillType": "Model.Post",
231
+ "Guid": "11111111-1111-1111-1111-111111111111",
232
+ "Properties": {"Title": "First", "Author": "A"},
233
+ },
234
+ },
235
+ {
236
+ "Index": 1,
237
+ "Verb": "create",
238
+ "Entity": {
239
+ "ChillType": "Model.Post",
240
+ "Guid": "22222222-2222-2222-2222-222222222222",
241
+ "Properties": {"Title": "Second", "Author": "B"},
242
+ },
243
+ },
244
+ {
245
+ "Index": 2,
246
+ "Verb": "update",
247
+ "Entity": {
248
+ "ChillType": "Model.Post",
249
+ "Guid": "11111111-1111-1111-1111-111111111111",
250
+ "Properties": {"Title": "First updated"},
251
+ },
252
+ },
253
+ ])
254
+ ```
255
+
256
+ ### Chunk inside one transaction
257
+
258
+ Wrap the batch with `transaction` and `commit` when all write operations must succeed or fail together.
259
+
260
+ ```python
261
+ operations = client.chunk([
262
+ {
263
+ "Index": 0,
264
+ "Verb": "transaction",
265
+ },
266
+ {
267
+ "Index": 1,
268
+ "Verb": "create",
269
+ "Entity": {
270
+ "ChillType": "Model.Blog",
271
+ "Guid": "33333333-3333-3333-3333-333333333333",
272
+ "Properties": {
273
+ "Name": "Batch blog",
274
+ "Url": "https://example.local/batch-blog",
275
+ },
276
+ },
277
+ },
278
+ {
279
+ "Index": 2,
280
+ "Verb": "create",
281
+ "Entity": {
282
+ "ChillType": "Model.Post",
283
+ "Guid": "44444444-4444-4444-4444-444444444444",
284
+ "Properties": {
285
+ "Title": "Batch post",
286
+ "Author": "Grace Hopper",
287
+ },
288
+ },
289
+ },
290
+ {
291
+ "Index": 3,
292
+ "Verb": "commit",
293
+ },
294
+ ])
295
+ ```
296
+
297
+ Use this pattern only for the operations that must share the same database transaction. If one write fails before `commit`, the transaction is not committed.
298
+
299
+ ## Schema Operations
300
+
301
+ ### Get schema
302
+
303
+ ```python
304
+ schema = client.get_schema("Model.Post", "default")
305
+ handle_attachments = schema.get("HandleAttachments") or schema.get("handleAttachments")
306
+ relations = schema.get("Relations") or schema.get("relations") or []
307
+
308
+ # Override the constructor default for one call
309
+ english_schema = client.get_schema("Model.Post", "default", culture_name="en-GB")
310
+
311
+ # Refresh a persisted schema from the current runtime model for one call.
312
+ # Existing properties keep their saved metadata, new model properties are added,
313
+ # and properties no longer present on the model are removed.
314
+ refreshed_schema = client.get_schema("Model.Post", "default", update=True)
315
+ ```
316
+
317
+ Entity schemas can also include `Relations` / `relations`, derived from annotated collection properties. Each item contains the child `ChillType`, the resolved `ChillQuery`, `FixedValues`, `FixedQueryValues`, and a `RelationLabel` object for UI wiring.
318
+
319
+ ### Get schema list
320
+
321
+ ```python
322
+ schema_list = client.get_schema_list()
323
+ english_schema_list = client.get_schema_list(culture_name="en-GB")
324
+ ```
325
+
326
+ ### Set schema
327
+
328
+ ```python
329
+ client.set_schema({
330
+ "ChillType": "Model.Post",
331
+ "ChillViewCode": "default",
332
+ "DisplayName": "Post",
333
+ "Properties": [
334
+ {
335
+ "Name": "Title",
336
+ "DisplayName": "Post title",
337
+ }
338
+ ],
339
+ })
340
+ ```
341
+
342
+ ### Get entity options
343
+
344
+ ```python
345
+ options = client.get_entity_options("Model.Post")
346
+ handle_attachments = options.get("HandleAttachments") or options.get("handleAttachments")
347
+ mcp_enabled = options.get("EnableMCP") or options.get("enableMCP")
348
+ mcp_description = options.get("MCPDescription") or options.get("mcpDescription")
349
+ ```
350
+
351
+ ### Set entity options
352
+
353
+ ```python
354
+ options = client.set_entity_options({
355
+ "ChillType": "Model.Post",
356
+ "ChecksumEnabled": True,
357
+ "HandleAttachments": True,
358
+ "LabelFormatString": "{Title}",
359
+ "ShortLabelFormatString": "{Title}",
360
+ "FullTextContentFormatString": "{Title} {Author}",
361
+ "EnableMCP": True,
362
+ "MCPDescription": "Post resource exposed to MCP clients.",
363
+ "ChangeLogEnabled": True,
364
+ })
365
+ ```
366
+
367
+ The Python client uses plain dictionaries for schema payloads, so `HandleAttachments`, `EnableMCP`, `MCPDescription`, and relation metadata are available without any client-side model regeneration.
368
+
369
+ ## I18n Operations
370
+
371
+ ### Get text
372
+
373
+ ```python
374
+ text = client.get_text({
375
+ "LabelGuid": "4e16f6c0-6b95-4d67-98bc-9f4d0d63eaf1",
376
+ "CultureName": "it-IT",
377
+ "PrimaryCultureName": "en-GB",
378
+ "PrimaryDefaultText": "Blog title",
379
+ "SecondaryCultureName": "it-IT",
380
+ "SecondaryDefaultText": "Titolo del blog",
381
+ })
382
+ ```
383
+
384
+ ### Set text
385
+
386
+ ```python
387
+ saved = client.set_text({
388
+ "LabelGuid": "4e16f6c0-6b95-4d67-98bc-9f4d0d63eaf1",
389
+ "CultureName": "it-IT",
390
+ "Value": "Titolo del blog",
391
+ })
392
+ ```
393
+
394
+ ## Auth Operations
395
+
396
+ The client assumes the auth base path is derived from `/api/chill` to `/api/chill-auth`, matching the .NET client.
397
+
398
+ ### Register account
399
+
400
+ ```python
401
+ token = client.register_auth_account({
402
+ "UserName": "root",
403
+ "Email": "root@example.com",
404
+ "Password": "Pass123$",
405
+ "DisplayName": "Root",
406
+ "DisplayCultureName": "it-IT",
407
+ "CreateChillAuthUser": True,
408
+ })
409
+ ```
410
+
411
+ If `DisplayCultureName` is provided and `CreateChillAuthUser` is `True`, the server presets the linked `AuthUser` with culture-based defaults for `DisplayTimeZone`, `DisplayDateFormat`, and `DisplayNumberFormat`.
412
+
413
+ ### Login
414
+
415
+ ```python
416
+ token = client.login_auth_account({
417
+ "UserNameOrEmail": "root",
418
+ "Password": "Pass123$",
419
+ })
420
+ ```
421
+
422
+ ### Refresh current token
423
+
424
+ ```python
425
+ token = client.refresh_auth_account()
426
+ ```
427
+
428
+ ### Change password
429
+
430
+ ```python
431
+ result = client.change_auth_password({
432
+ "CurrentPassword": "Pass123$",
433
+ "NewPassword": "Pass456$",
434
+ })
435
+ ```
436
+
437
+ ### Request password reset
438
+
439
+ ```python
440
+ reset_token = client.request_auth_password_reset({
441
+ "UserNameOrEmail": "root",
442
+ })
443
+ ```
444
+
445
+ ### Reset password
446
+
447
+ ```python
448
+ result = client.reset_auth_password({
449
+ "UserId": reset_token["UserId"],
450
+ "ResetToken": reset_token["ResetToken"],
451
+ "NewPassword": "Pass789$",
452
+ })
453
+ ```
454
+
455
+ ## Auth Management Operations
456
+
457
+ Use these endpoints when the host exposes ChillSharp auth management APIs.
458
+
459
+ ### Get current permissions
460
+
461
+ ```python
462
+ permissions = client.get_auth_permissions()
463
+ ```
464
+
465
+ ### Get user list
466
+
467
+ ```python
468
+ users = client.get_auth_user_list()
469
+ ```
470
+
471
+ Auth user list/detail payloads include `DisplayCultureName`, `DisplayTimeZone`, `DisplayDateFormat`, and `DisplayNumberFormat`.
472
+
473
+ Auth user and role payloads also include `MenuHierarchy`, which is used by the schema menu model to filter visible menu nodes. See [../../doc/MenuGuide/README.md](../../doc/MenuGuide/README.md).
474
+
475
+ ### Get managed user
476
+
477
+ ```python
478
+ user = client.get_auth_user("f2d5d5e3-0a1f-4d15-9396-2ab5f6c4ff11")
479
+ ```
480
+
481
+ ### Set managed user
482
+
483
+ ```python
484
+ user = client.set_auth_user({
485
+ "Guid": None,
486
+ "ExternalId": "identity-user-001",
487
+ "UserName": "identity.user",
488
+ "DisplayName": "Identity User",
489
+ "DisplayCultureName": "it-IT",
490
+ "DisplayTimeZone": "W. Europe Standard Time",
491
+ "DisplayDateFormat": "DD/MM/YYYY",
492
+ "DisplayNumberFormat": "1.000,00",
493
+ "IsActive": True,
494
+ "CanManagePermissions": False,
495
+ "CanManageSchema": True,
496
+ "RoleGuids": [],
497
+ "Permissions": [],
498
+ })
499
+ ```
500
+
501
+ ### Get role list
502
+
503
+ ```python
504
+ roles = client.get_auth_role_list()
505
+ ```
506
+
507
+ ### Get managed role
508
+
509
+ ```python
510
+ role = client.get_auth_role("e2f0d8d5-0a1f-4d15-9396-2ab5f6c4ff22")
511
+ ```
512
+
513
+ ### Set managed role
514
+
515
+ ```python
516
+ role = client.set_auth_role({
517
+ "Guid": None,
518
+ "Name": "Editors",
519
+ "Description": "Can edit posts",
520
+ "IsActive": True,
521
+ "UserGuids": [],
522
+ "Permissions": [],
523
+ })
524
+ ```
525
+
526
+ ## Accessing The Underlying Session
527
+
528
+ If you need custom headers, proxies, or retries, use the exposed `session`:
529
+
530
+ ```python
531
+ client.session.headers["X-Correlation-Id"] = "demo-123"
532
+ ```
533
+
534
+ ## Error Handling
535
+
536
+ All request failures raise `ChillSharpClientError`.
537
+
538
+ ```python
539
+ from chillsharp_py_client import ChillSharpClient, ChillSharpClientError
540
+
541
+ client = ChillSharpClient("http://localhost:5000/api/chill", culture_name="it-IT")
542
+
543
+ try:
544
+ client.get_schema("Model.Post", "default")
545
+ except ChillSharpClientError as exc:
546
+ print(exc.status_code)
547
+ print(exc.response_text)
548
+ ```
549
+
550
+ ## Generic Payload Strategy
551
+
552
+ This package does not generate Python model classes for your Chill entities.
553
+
554
+ That is intentional:
555
+
556
+ - ChillSharp models are application-specific
557
+ - the standard Chill API already works well with generic dictionaries
558
+ - a generic client is easier to reuse across many different ChillSharp services
559
+
560
+ If you need strongly typed Python clients, generate them from your host OpenAPI document as described in [doc/ClientGeneration/README.md](../../doc/ClientGeneration/README.md).
561
+
562
+
563
+
564
+
565
+
566
+
567
+