unique_toolkit 1.2.1__py3-none-any.whl → 1.3.1__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.
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/content/schemas.py +48 -12
- {unique_toolkit-1.2.1.dist-info → unique_toolkit-1.3.1.dist-info}/METADATA +8 -1
- {unique_toolkit-1.2.1.dist-info → unique_toolkit-1.3.1.dist-info}/RECORD +6 -5
- {unique_toolkit-1.2.1.dist-info → unique_toolkit-1.3.1.dist-info}/LICENSE +0 -0
- {unique_toolkit-1.2.1.dist-info → unique_toolkit-1.3.1.dist-info}/WHEEL +0 -0
@@ -0,0 +1,936 @@
|
|
1
|
+
"""
|
2
|
+
React JSON Schema Form (RJSF) metadata tags for Pydantic models.
|
3
|
+
|
4
|
+
This module provides utilities for generating React JSON Schema Form uiSchemas
|
5
|
+
from Pydantic models using type annotations. It allows developers to specify
|
6
|
+
UI metadata directly in their Pydantic model definitions using Annotated types.
|
7
|
+
|
8
|
+
Key components:
|
9
|
+
- RJSFMetaTag: Factory class for creating RJSF metadata tags
|
10
|
+
- ui_schema_for_model(): Main function to generate uiSchema from Pydantic models
|
11
|
+
- Helper functions for processing type annotations and metadata
|
12
|
+
"""
|
13
|
+
|
14
|
+
from __future__ import annotations
|
15
|
+
|
16
|
+
from typing import Annotated, Any, Union, get_args, get_origin
|
17
|
+
|
18
|
+
from pydantic import BaseModel
|
19
|
+
from typing_extensions import get_type_hints
|
20
|
+
|
21
|
+
|
22
|
+
# --------- Base Metadata carrier ----------
|
23
|
+
class RJSFMetaTag:
|
24
|
+
def __init__(self, attrs: dict[str, Any] | None = None):
|
25
|
+
"""Initialize with either a dict or keyword arguments.
|
26
|
+
|
27
|
+
Args:
|
28
|
+
attrs: Dictionary of attributes (preferred)
|
29
|
+
**kwargs: Keyword arguments (for backward compatibility)
|
30
|
+
"""
|
31
|
+
self.attrs = attrs if attrs is not None else {}
|
32
|
+
|
33
|
+
# --------- Widget Type Subclasses ----------
|
34
|
+
|
35
|
+
class BooleanWidget:
|
36
|
+
"""Widgets for boolean fields: radio, select, checkbox (default)."""
|
37
|
+
|
38
|
+
@classmethod
|
39
|
+
def radio(
|
40
|
+
cls,
|
41
|
+
*,
|
42
|
+
disabled: bool = False,
|
43
|
+
title: str | None = None,
|
44
|
+
description: str | None = None,
|
45
|
+
help: str | None = None,
|
46
|
+
**kwargs: Any,
|
47
|
+
) -> RJSFMetaTag:
|
48
|
+
"""Create a radio button group for boolean values."""
|
49
|
+
attrs = {
|
50
|
+
"ui:widget": "radio",
|
51
|
+
"ui:disabled": disabled,
|
52
|
+
"ui:title": title,
|
53
|
+
"ui:description": description,
|
54
|
+
"ui:help": help,
|
55
|
+
**kwargs,
|
56
|
+
}
|
57
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
58
|
+
|
59
|
+
@classmethod
|
60
|
+
def select(
|
61
|
+
cls,
|
62
|
+
*,
|
63
|
+
disabled: bool = False,
|
64
|
+
title: str | None = None,
|
65
|
+
description: str | None = None,
|
66
|
+
help: str | None = None,
|
67
|
+
**kwargs: Any,
|
68
|
+
) -> RJSFMetaTag:
|
69
|
+
"""Create a select dropdown for boolean values."""
|
70
|
+
attrs = {
|
71
|
+
"ui:widget": "select",
|
72
|
+
"ui:disabled": disabled,
|
73
|
+
"ui:title": title,
|
74
|
+
"ui:description": description,
|
75
|
+
"ui:help": help,
|
76
|
+
**kwargs,
|
77
|
+
}
|
78
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def checkbox(
|
82
|
+
cls,
|
83
|
+
*,
|
84
|
+
disabled: bool = False,
|
85
|
+
title: str | None = None,
|
86
|
+
description: str | None = None,
|
87
|
+
help: str | None = None,
|
88
|
+
**kwargs: Any,
|
89
|
+
) -> RJSFMetaTag:
|
90
|
+
"""Create a checkbox for boolean values (default widget)."""
|
91
|
+
attrs = {
|
92
|
+
"ui:widget": "checkbox",
|
93
|
+
"ui:disabled": disabled,
|
94
|
+
"ui:title": title,
|
95
|
+
"ui:description": description,
|
96
|
+
"ui:help": help,
|
97
|
+
**kwargs,
|
98
|
+
}
|
99
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
100
|
+
|
101
|
+
class StringWidget:
|
102
|
+
"""Widgets for string fields: text, textarea, password, color, email, url, date, datetime, time, file."""
|
103
|
+
|
104
|
+
@classmethod
|
105
|
+
def textfield(
|
106
|
+
cls,
|
107
|
+
*,
|
108
|
+
placeholder: str | None = None,
|
109
|
+
disabled: bool = False,
|
110
|
+
readonly: bool = False,
|
111
|
+
autofocus: bool = False,
|
112
|
+
title: str | None = None,
|
113
|
+
description: str | None = None,
|
114
|
+
help: str | None = None,
|
115
|
+
class_names: str | None = None,
|
116
|
+
**kwargs: Any,
|
117
|
+
) -> RJSFMetaTag:
|
118
|
+
"""Create a text field (default for strings)."""
|
119
|
+
attrs: dict[str, Any] = {
|
120
|
+
"ui:widget": "text",
|
121
|
+
"ui:placeholder": placeholder,
|
122
|
+
"ui:disabled": disabled,
|
123
|
+
"ui:readonly": readonly,
|
124
|
+
"ui:autofocus": autofocus,
|
125
|
+
"ui:title": title,
|
126
|
+
"ui:description": description,
|
127
|
+
"ui:help": help,
|
128
|
+
"ui:classNames": class_names,
|
129
|
+
**kwargs,
|
130
|
+
}
|
131
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
132
|
+
|
133
|
+
@classmethod
|
134
|
+
def textarea(
|
135
|
+
cls,
|
136
|
+
*,
|
137
|
+
placeholder: str | None = None,
|
138
|
+
disabled: bool = False,
|
139
|
+
readonly: bool = False,
|
140
|
+
rows: int | None = None,
|
141
|
+
title: str | None = None,
|
142
|
+
description: str | None = None,
|
143
|
+
help: str | None = None,
|
144
|
+
**kwargs: Any,
|
145
|
+
) -> RJSFMetaTag:
|
146
|
+
"""Create a textarea field."""
|
147
|
+
attrs: dict[str, Any] = {
|
148
|
+
"ui:widget": "textarea",
|
149
|
+
"ui:placeholder": placeholder,
|
150
|
+
"ui:disabled": disabled,
|
151
|
+
"ui:readonly": readonly,
|
152
|
+
"ui:options": {"rows": rows} if rows else None,
|
153
|
+
"ui:title": title,
|
154
|
+
"ui:description": description,
|
155
|
+
"ui:help": help,
|
156
|
+
**kwargs,
|
157
|
+
}
|
158
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
159
|
+
|
160
|
+
@classmethod
|
161
|
+
def password(
|
162
|
+
cls,
|
163
|
+
*,
|
164
|
+
placeholder: str | None = None,
|
165
|
+
disabled: bool = False,
|
166
|
+
readonly: bool = False,
|
167
|
+
title: str | None = None,
|
168
|
+
description: str | None = None,
|
169
|
+
help: str | None = None,
|
170
|
+
**kwargs: Any,
|
171
|
+
) -> RJSFMetaTag:
|
172
|
+
"""Create a password field."""
|
173
|
+
attrs = {
|
174
|
+
"ui:widget": "password",
|
175
|
+
"ui:placeholder": placeholder,
|
176
|
+
"ui:disabled": disabled,
|
177
|
+
"ui:readonly": readonly,
|
178
|
+
"ui:title": title,
|
179
|
+
"ui:description": description,
|
180
|
+
"ui:help": help,
|
181
|
+
**kwargs,
|
182
|
+
}
|
183
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
184
|
+
|
185
|
+
@classmethod
|
186
|
+
def color(
|
187
|
+
cls,
|
188
|
+
*,
|
189
|
+
disabled: bool = False,
|
190
|
+
title: str | None = None,
|
191
|
+
description: str | None = None,
|
192
|
+
help: str | None = None,
|
193
|
+
**kwargs: Any,
|
194
|
+
) -> RJSFMetaTag:
|
195
|
+
"""Create a color picker field."""
|
196
|
+
attrs = {
|
197
|
+
"ui:widget": "color",
|
198
|
+
"ui:disabled": disabled,
|
199
|
+
"ui:title": title,
|
200
|
+
"ui:description": description,
|
201
|
+
"ui:help": help,
|
202
|
+
**kwargs,
|
203
|
+
}
|
204
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
205
|
+
|
206
|
+
@classmethod
|
207
|
+
def email(
|
208
|
+
cls,
|
209
|
+
*,
|
210
|
+
placeholder: str | None = None,
|
211
|
+
disabled: bool = False,
|
212
|
+
readonly: bool = False,
|
213
|
+
title: str | None = None,
|
214
|
+
description: str | None = None,
|
215
|
+
help: str | None = None,
|
216
|
+
**kwargs: Any,
|
217
|
+
) -> RJSFMetaTag:
|
218
|
+
"""Create an email field."""
|
219
|
+
attrs = {
|
220
|
+
"ui:widget": "email",
|
221
|
+
"ui:placeholder": placeholder,
|
222
|
+
"ui:disabled": disabled,
|
223
|
+
"ui:readonly": readonly,
|
224
|
+
"ui:title": title,
|
225
|
+
"ui:description": description,
|
226
|
+
"ui:help": help,
|
227
|
+
**kwargs,
|
228
|
+
}
|
229
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
230
|
+
|
231
|
+
@classmethod
|
232
|
+
def url(
|
233
|
+
cls,
|
234
|
+
*,
|
235
|
+
placeholder: str | None = None,
|
236
|
+
disabled: bool = False,
|
237
|
+
readonly: bool = False,
|
238
|
+
title: str | None = None,
|
239
|
+
description: str | None = None,
|
240
|
+
help: str | None = None,
|
241
|
+
**kwargs: Any,
|
242
|
+
) -> RJSFMetaTag:
|
243
|
+
"""Create a URL field."""
|
244
|
+
attrs = {
|
245
|
+
"ui:widget": "uri",
|
246
|
+
"ui:placeholder": placeholder,
|
247
|
+
"ui:disabled": disabled,
|
248
|
+
"ui:readonly": readonly,
|
249
|
+
"ui:title": title,
|
250
|
+
"ui:description": description,
|
251
|
+
"ui:help": help,
|
252
|
+
**kwargs,
|
253
|
+
}
|
254
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
255
|
+
|
256
|
+
@classmethod
|
257
|
+
def date(
|
258
|
+
cls,
|
259
|
+
*,
|
260
|
+
disabled: bool = False,
|
261
|
+
title: str | None = None,
|
262
|
+
description: str | None = None,
|
263
|
+
help: str | None = None,
|
264
|
+
**kwargs: Any,
|
265
|
+
) -> RJSFMetaTag:
|
266
|
+
"""Create a date field."""
|
267
|
+
attrs = {
|
268
|
+
"ui:widget": "date",
|
269
|
+
"ui:disabled": disabled,
|
270
|
+
"ui:title": title,
|
271
|
+
"ui:description": description,
|
272
|
+
"ui:help": help,
|
273
|
+
**kwargs,
|
274
|
+
}
|
275
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
276
|
+
|
277
|
+
@classmethod
|
278
|
+
def datetime(
|
279
|
+
cls,
|
280
|
+
*,
|
281
|
+
disabled: bool = False,
|
282
|
+
title: str | None = None,
|
283
|
+
description: str | None = None,
|
284
|
+
help: str | None = None,
|
285
|
+
**kwargs: Any,
|
286
|
+
) -> RJSFMetaTag:
|
287
|
+
"""Create a datetime field."""
|
288
|
+
attrs = {
|
289
|
+
"ui:widget": "datetime",
|
290
|
+
"ui:disabled": disabled,
|
291
|
+
"ui:title": title,
|
292
|
+
"ui:description": description,
|
293
|
+
"ui:help": help,
|
294
|
+
**kwargs,
|
295
|
+
}
|
296
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
297
|
+
|
298
|
+
@classmethod
|
299
|
+
def time(
|
300
|
+
cls,
|
301
|
+
*,
|
302
|
+
disabled: bool = False,
|
303
|
+
title: str | None = None,
|
304
|
+
description: str | None = None,
|
305
|
+
help: str | None = None,
|
306
|
+
**kwargs: Any,
|
307
|
+
) -> RJSFMetaTag:
|
308
|
+
"""Create a time field."""
|
309
|
+
attrs = {
|
310
|
+
"ui:widget": "time",
|
311
|
+
"ui:disabled": disabled,
|
312
|
+
"ui:title": title,
|
313
|
+
"ui:description": description,
|
314
|
+
"ui:help": help,
|
315
|
+
**kwargs,
|
316
|
+
}
|
317
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
318
|
+
|
319
|
+
@classmethod
|
320
|
+
def file(
|
321
|
+
cls,
|
322
|
+
*,
|
323
|
+
disabled: bool = False,
|
324
|
+
accept: str | None = None,
|
325
|
+
title: str | None = None,
|
326
|
+
description: str | None = None,
|
327
|
+
help: str | None = None,
|
328
|
+
**kwargs: Any,
|
329
|
+
) -> RJSFMetaTag:
|
330
|
+
"""Create a file upload field."""
|
331
|
+
attrs = {
|
332
|
+
"ui:widget": "file",
|
333
|
+
"ui:disabled": disabled,
|
334
|
+
"ui:options": {"accept": accept} if accept else None,
|
335
|
+
"ui:title": title,
|
336
|
+
"ui:description": description,
|
337
|
+
"ui:help": help,
|
338
|
+
**kwargs,
|
339
|
+
}
|
340
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
341
|
+
|
342
|
+
class NumberWidget:
|
343
|
+
"""Widgets for number and integer fields: updown, range, radio (with enum)."""
|
344
|
+
|
345
|
+
@classmethod
|
346
|
+
def updown(
|
347
|
+
cls,
|
348
|
+
*,
|
349
|
+
placeholder: str | None = None,
|
350
|
+
disabled: bool = False,
|
351
|
+
readonly: bool = False,
|
352
|
+
min: int | float | None = None,
|
353
|
+
max: int | float | None = None,
|
354
|
+
step: int | float | None = None,
|
355
|
+
title: str | None = None,
|
356
|
+
description: str | None = None,
|
357
|
+
help: str | None = None,
|
358
|
+
**kwargs: Any,
|
359
|
+
) -> RJSFMetaTag:
|
360
|
+
"""Create a number updown field (default for numbers)."""
|
361
|
+
options: dict[str, Any] = {
|
362
|
+
"min": min,
|
363
|
+
"max": max,
|
364
|
+
"step": step,
|
365
|
+
}
|
366
|
+
options = {k: v for k, v in options.items() if v is not None}
|
367
|
+
|
368
|
+
attrs = {
|
369
|
+
"ui:widget": "updown",
|
370
|
+
"ui:placeholder": placeholder,
|
371
|
+
"ui:disabled": disabled,
|
372
|
+
"ui:readonly": readonly,
|
373
|
+
"ui:options": options if options else None,
|
374
|
+
"ui:title": title,
|
375
|
+
"ui:description": description,
|
376
|
+
"ui:help": help,
|
377
|
+
**kwargs,
|
378
|
+
}
|
379
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
380
|
+
|
381
|
+
@classmethod
|
382
|
+
def range(
|
383
|
+
cls,
|
384
|
+
*,
|
385
|
+
disabled: bool = False,
|
386
|
+
min: int | float | None = None,
|
387
|
+
max: int | float | None = None,
|
388
|
+
step: int | float | None = None,
|
389
|
+
title: str | None = None,
|
390
|
+
description: str | None = None,
|
391
|
+
help: str | None = None,
|
392
|
+
**kwargs: Any,
|
393
|
+
) -> RJSFMetaTag:
|
394
|
+
"""Create a range slider field."""
|
395
|
+
options: dict[str, Any] = {
|
396
|
+
"min": min,
|
397
|
+
"max": max,
|
398
|
+
"step": step,
|
399
|
+
}
|
400
|
+
options = {k: v for k, v in options.items() if v is not None}
|
401
|
+
|
402
|
+
attrs = {
|
403
|
+
"ui:widget": "range",
|
404
|
+
"ui:disabled": str(disabled).lower() if disabled else None,
|
405
|
+
"ui:options": options if options else None,
|
406
|
+
"ui:title": title,
|
407
|
+
"ui:description": description,
|
408
|
+
"ui:help": help,
|
409
|
+
**kwargs,
|
410
|
+
}
|
411
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
412
|
+
|
413
|
+
@classmethod
|
414
|
+
def radio(
|
415
|
+
cls,
|
416
|
+
*,
|
417
|
+
disabled: bool = False,
|
418
|
+
title: str | None = None,
|
419
|
+
description: str | None = None,
|
420
|
+
help: str | None = None,
|
421
|
+
**kwargs: Any,
|
422
|
+
) -> RJSFMetaTag:
|
423
|
+
"""Create radio buttons for number enum values."""
|
424
|
+
attrs = {
|
425
|
+
"ui:widget": "radio",
|
426
|
+
"ui:disabled": disabled,
|
427
|
+
"ui:title": title,
|
428
|
+
"ui:description": description,
|
429
|
+
"ui:help": help,
|
430
|
+
**kwargs,
|
431
|
+
}
|
432
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
433
|
+
|
434
|
+
class ArrayWidget:
|
435
|
+
"""Widgets for array fields: checkboxes, select, radio."""
|
436
|
+
|
437
|
+
@classmethod
|
438
|
+
def checkboxes(
|
439
|
+
cls,
|
440
|
+
*,
|
441
|
+
disabled: bool = False,
|
442
|
+
title: str | None = None,
|
443
|
+
description: str | None = None,
|
444
|
+
help: str | None = None,
|
445
|
+
**kwargs: Any,
|
446
|
+
) -> RJSFMetaTag:
|
447
|
+
"""Create checkboxes for array values."""
|
448
|
+
attrs = {
|
449
|
+
"ui:widget": "checkboxes",
|
450
|
+
"ui:disabled": disabled,
|
451
|
+
"ui:title": title,
|
452
|
+
"ui:description": description,
|
453
|
+
"ui:help": help,
|
454
|
+
**kwargs,
|
455
|
+
}
|
456
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
457
|
+
|
458
|
+
@classmethod
|
459
|
+
def select(
|
460
|
+
cls,
|
461
|
+
*,
|
462
|
+
disabled: bool = False,
|
463
|
+
title: str | None = None,
|
464
|
+
description: str | None = None,
|
465
|
+
help: str | None = None,
|
466
|
+
**kwargs: Any,
|
467
|
+
) -> RJSFMetaTag:
|
468
|
+
"""Create a select dropdown for array values."""
|
469
|
+
attrs = {
|
470
|
+
"ui:widget": "select",
|
471
|
+
"ui:disabled": disabled,
|
472
|
+
"ui:title": title,
|
473
|
+
"ui:description": description,
|
474
|
+
"ui:help": help,
|
475
|
+
**kwargs,
|
476
|
+
}
|
477
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
478
|
+
|
479
|
+
@classmethod
|
480
|
+
def radio(
|
481
|
+
cls,
|
482
|
+
*,
|
483
|
+
disabled: bool = False,
|
484
|
+
title: str | None = None,
|
485
|
+
description: str | None = None,
|
486
|
+
help: str | None = None,
|
487
|
+
**kwargs: Any,
|
488
|
+
) -> RJSFMetaTag:
|
489
|
+
"""Create radio buttons for array values."""
|
490
|
+
attrs = {
|
491
|
+
"ui:widget": "radio",
|
492
|
+
"ui:disabled": disabled,
|
493
|
+
"ui:title": title,
|
494
|
+
"ui:description": description,
|
495
|
+
"ui:help": help,
|
496
|
+
**kwargs,
|
497
|
+
}
|
498
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
499
|
+
|
500
|
+
class ObjectWidget:
|
501
|
+
"""Widgets for object fields: expandable, collapsible."""
|
502
|
+
|
503
|
+
@classmethod
|
504
|
+
def expandable(
|
505
|
+
cls,
|
506
|
+
*,
|
507
|
+
title: str | None = None,
|
508
|
+
description: str | None = None,
|
509
|
+
help: str | None = None,
|
510
|
+
**kwargs: Any,
|
511
|
+
) -> RJSFMetaTag:
|
512
|
+
"""Create an expandable object field."""
|
513
|
+
attrs = {
|
514
|
+
"ui:expandable": True,
|
515
|
+
"ui:title": title,
|
516
|
+
"ui:description": description,
|
517
|
+
"ui:help": help,
|
518
|
+
**kwargs,
|
519
|
+
}
|
520
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
521
|
+
|
522
|
+
@classmethod
|
523
|
+
def collapsible(
|
524
|
+
cls,
|
525
|
+
*,
|
526
|
+
title: str | None = None,
|
527
|
+
description: str | None = None,
|
528
|
+
help: str | None = None,
|
529
|
+
**kwargs: Any,
|
530
|
+
) -> RJSFMetaTag:
|
531
|
+
"""Create a collapsible object field."""
|
532
|
+
attrs = {
|
533
|
+
"ui:collapsible": True,
|
534
|
+
"ui:title": title,
|
535
|
+
"ui:description": description,
|
536
|
+
"ui:help": help,
|
537
|
+
**kwargs,
|
538
|
+
}
|
539
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
540
|
+
|
541
|
+
class SpecialWidget:
|
542
|
+
"""Special widgets: hidden, custom fields."""
|
543
|
+
|
544
|
+
@classmethod
|
545
|
+
def hidden(
|
546
|
+
cls,
|
547
|
+
**kwargs: Any,
|
548
|
+
) -> RJSFMetaTag:
|
549
|
+
"""Create a hidden field."""
|
550
|
+
attrs = {
|
551
|
+
"ui:widget": "hidden",
|
552
|
+
**kwargs,
|
553
|
+
}
|
554
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
555
|
+
|
556
|
+
@classmethod
|
557
|
+
def custom_field(
|
558
|
+
cls,
|
559
|
+
field_name: str,
|
560
|
+
*,
|
561
|
+
title: str | None = None,
|
562
|
+
description: str | None = None,
|
563
|
+
help: str | None = None,
|
564
|
+
**kwargs: Any,
|
565
|
+
) -> RJSFMetaTag:
|
566
|
+
"""Create a custom field."""
|
567
|
+
attrs = {
|
568
|
+
"ui:field": field_name,
|
569
|
+
"ui:title": title,
|
570
|
+
"ui:description": description,
|
571
|
+
"ui:help": help,
|
572
|
+
**kwargs,
|
573
|
+
}
|
574
|
+
return RJSFMetaTag({k: v for k, v in attrs.items() if v is not None})
|
575
|
+
|
576
|
+
# --------- Composer Methods ----------
|
577
|
+
|
578
|
+
@classmethod
|
579
|
+
def Optional(
|
580
|
+
cls,
|
581
|
+
widget_tag: RJSFMetaTag,
|
582
|
+
/,
|
583
|
+
title: str | None = None,
|
584
|
+
description: str | None = None,
|
585
|
+
help: str | None = None,
|
586
|
+
disabled: bool = False,
|
587
|
+
readonly: bool = False,
|
588
|
+
optional_title: str | None = None,
|
589
|
+
**kwargs: Any,
|
590
|
+
) -> RJSFMetaTag:
|
591
|
+
"""
|
592
|
+
Create an Optional widget that applies the given widget to the non-None type.
|
593
|
+
|
594
|
+
Args:
|
595
|
+
widget_tag: The widget tag to apply to the Optional type
|
596
|
+
|
597
|
+
Returns:
|
598
|
+
RJSFMetaTag with the widget applied to the Optional type
|
599
|
+
"""
|
600
|
+
|
601
|
+
union_attrs = {
|
602
|
+
"ui:title": title,
|
603
|
+
"ui:description": description,
|
604
|
+
"ui:help": help,
|
605
|
+
"ui:disabled": disabled,
|
606
|
+
"ui:readonly": readonly,
|
607
|
+
**kwargs,
|
608
|
+
}
|
609
|
+
|
610
|
+
union_attrs = {k: v for k, v in union_attrs.items() if v is not None}
|
611
|
+
|
612
|
+
optional_attrs = {
|
613
|
+
"ui:title": optional_title,
|
614
|
+
}
|
615
|
+
optional_attrs = {k: v for k, v in optional_attrs.items() if v is not None}
|
616
|
+
return RJSFMetaTag(
|
617
|
+
{
|
618
|
+
**union_attrs,
|
619
|
+
"anyOf": [{**widget_tag.attrs}, {**optional_attrs, "type": "null"}],
|
620
|
+
}
|
621
|
+
)
|
622
|
+
|
623
|
+
@classmethod
|
624
|
+
def Union(
|
625
|
+
cls,
|
626
|
+
widgets: list[RJSFMetaTag],
|
627
|
+
/,
|
628
|
+
title: str | None = None,
|
629
|
+
description: str | None = None,
|
630
|
+
help: str | None = None,
|
631
|
+
disabled: bool = False,
|
632
|
+
readonly: bool = False,
|
633
|
+
**kwargs: Any,
|
634
|
+
) -> RJSFMetaTag:
|
635
|
+
"""
|
636
|
+
Create a Union widget that applies different widgets to each branch of the Union.
|
637
|
+
|
638
|
+
According to RJSF docs, anyOf should be at the same level as the field,
|
639
|
+
not nested inside the field metadata.
|
640
|
+
|
641
|
+
Args:
|
642
|
+
widgets: List of widget tags for each branch of the Union type
|
643
|
+
title: Title for the Union field
|
644
|
+
description: Description for the Union field
|
645
|
+
help: Help text for the Union field
|
646
|
+
disabled: Whether the Union field is disabled
|
647
|
+
readonly: Whether the Union field is readonly
|
648
|
+
**kwargs: Additional Union-level metadata
|
649
|
+
|
650
|
+
Returns:
|
651
|
+
RJSFMetaTag with anyOf structure containing widgets for each branch
|
652
|
+
"""
|
653
|
+
|
654
|
+
if len(widgets) == 1:
|
655
|
+
raise ValueError(
|
656
|
+
"Union types require multiple widgets (one per branch). "
|
657
|
+
"For single widget usage, consider using Optional() or a regular widget. "
|
658
|
+
f"Received only 1 widget: {widgets[0].attrs.get('ui:widget', 'unknown')}"
|
659
|
+
)
|
660
|
+
|
661
|
+
# Create anyOf structure with the provided widgets
|
662
|
+
any_of_branches = []
|
663
|
+
for widget in widgets:
|
664
|
+
any_of_branches.append(widget.attrs)
|
665
|
+
|
666
|
+
# Start with Union-level metadata
|
667
|
+
union_attrs = {
|
668
|
+
"ui:title": title,
|
669
|
+
"ui:description": description,
|
670
|
+
"ui:help": help,
|
671
|
+
"ui:disabled": disabled,
|
672
|
+
"ui:readonly": readonly,
|
673
|
+
**kwargs,
|
674
|
+
}
|
675
|
+
|
676
|
+
# Add any additional kwargs
|
677
|
+
for key, value in kwargs.items():
|
678
|
+
if value is not None:
|
679
|
+
union_attrs[f"ui:{key}" if not key.startswith("ui:") else key] = value
|
680
|
+
|
681
|
+
union_attrs = {k: v for k, v in union_attrs.items() if v is not None}
|
682
|
+
return RJSFMetaTag({**union_attrs, "anyOf": any_of_branches})
|
683
|
+
|
684
|
+
|
685
|
+
# --------- Helpers ----------
|
686
|
+
_NONE_TYPES = {type(None)}
|
687
|
+
"""
|
688
|
+
Set containing the None type for use in Union type processing.
|
689
|
+
Used by _unwrap_optional to identify and filter out None types from Union annotations.
|
690
|
+
"""
|
691
|
+
|
692
|
+
|
693
|
+
def _strip_annotated(ann: Any) -> tuple[Any, list[Any]]:
|
694
|
+
"""
|
695
|
+
Extract the base type and metadata from an Annotated type.
|
696
|
+
|
697
|
+
This function unwraps a single level of Annotated[...] to get the base type
|
698
|
+
and any metadata annotations. For non-Annotated types, it returns the
|
699
|
+
type unchanged with an empty metadata list.
|
700
|
+
|
701
|
+
Args:
|
702
|
+
ann: The type annotation to process (may be Annotated or not)
|
703
|
+
|
704
|
+
Returns:
|
705
|
+
A tuple of (base_type, metadata_list) where:
|
706
|
+
- base_type: The underlying type (e.g., str, int, list[str])
|
707
|
+
- metadata_list: List of metadata objects (RJSFMetaTag instances, etc.)
|
708
|
+
"""
|
709
|
+
if get_origin(ann) is Annotated:
|
710
|
+
base, *extras = get_args(ann)
|
711
|
+
return base, extras
|
712
|
+
return ann, []
|
713
|
+
|
714
|
+
|
715
|
+
def _collect_metatags(extras: list[Any]) -> dict[str, Any]:
|
716
|
+
"""
|
717
|
+
Extract and merge RJSF metadata from a list of annotation extras.
|
718
|
+
|
719
|
+
This function processes a list of metadata objects (from Annotated[...])
|
720
|
+
and extracts all RJSFMetaTag instances, merging their attributes into
|
721
|
+
a single dictionary. Later tags override earlier ones for duplicate keys.
|
722
|
+
|
723
|
+
Args:
|
724
|
+
extras: List of metadata objects from Annotated type annotations
|
725
|
+
|
726
|
+
Returns:
|
727
|
+
A dictionary containing all merged RJSF metadata attributes
|
728
|
+
"""
|
729
|
+
out: dict[str, Any] = {}
|
730
|
+
for x in extras:
|
731
|
+
if isinstance(x, RJSFMetaTag):
|
732
|
+
out.update(x.attrs)
|
733
|
+
return out
|
734
|
+
|
735
|
+
|
736
|
+
def _unwrap_optional(ann: Any) -> Any:
|
737
|
+
"""
|
738
|
+
Unwrap Optional[Type] or Union[Type, None] to just Type.
|
739
|
+
|
740
|
+
This function simplifies Union types that contain None by removing the
|
741
|
+
None option and returning the non-None type. If there are multiple
|
742
|
+
non-None types, the original Union is returned unchanged.
|
743
|
+
|
744
|
+
Args:
|
745
|
+
ann: The type annotation to process
|
746
|
+
|
747
|
+
Returns:
|
748
|
+
The unwrapped type if it was Optional/Union with None, otherwise
|
749
|
+
the original type unchanged
|
750
|
+
"""
|
751
|
+
if get_origin(ann) is Union:
|
752
|
+
args = [a for a in get_args(ann) if a not in _NONE_TYPES]
|
753
|
+
if len(args) == 1:
|
754
|
+
return args[0]
|
755
|
+
return ann
|
756
|
+
|
757
|
+
|
758
|
+
def _walk_annotated_chain(ann: Any) -> tuple[Any, dict[str, Any]]:
|
759
|
+
"""
|
760
|
+
Recursively unwrap nested Annotated types and collect all metadata.
|
761
|
+
|
762
|
+
This function processes deeply nested Annotated[...] types by walking
|
763
|
+
through the chain and collecting all RJSF metadata from each level.
|
764
|
+
It handles cases like Annotated[Annotated[Type, meta1], meta2].
|
765
|
+
|
766
|
+
Args:
|
767
|
+
ann: The type annotation to process (may be deeply nested)
|
768
|
+
|
769
|
+
Returns:
|
770
|
+
A tuple of (final_base_type, merged_metadata_dict) where:
|
771
|
+
- final_base_type: The innermost non-Annotated type
|
772
|
+
- merged_metadata_dict: All RJSF metadata merged from all levels
|
773
|
+
"""
|
774
|
+
merged: dict[str, Any] = {}
|
775
|
+
cur = ann
|
776
|
+
while get_origin(cur) is Annotated:
|
777
|
+
base, extras = _strip_annotated(cur)
|
778
|
+
merged.update(_collect_metatags(extras))
|
779
|
+
cur = base
|
780
|
+
return cur, merged
|
781
|
+
|
782
|
+
|
783
|
+
def _is_pyd_model(t: Any) -> bool:
|
784
|
+
try:
|
785
|
+
return isinstance(t, type) and issubclass(t, BaseModel)
|
786
|
+
except TypeError:
|
787
|
+
return False
|
788
|
+
|
789
|
+
|
790
|
+
# --------- Build RJSF-style uiSchema dict from a model *type* ----------
|
791
|
+
def ui_schema_for_model(model_cls: type[BaseModel]) -> dict[str, Any]:
|
792
|
+
"""
|
793
|
+
Generate a React JSON Schema Form (RJSF) uiSchema from a Pydantic model.
|
794
|
+
|
795
|
+
This function analyzes a Pydantic BaseModel class and extracts all RJSF metadata
|
796
|
+
from field annotations to create a complete uiSchema dictionary. The resulting
|
797
|
+
schema can be used directly with React JSON Schema Form components.
|
798
|
+
|
799
|
+
The function handles:
|
800
|
+
- Simple fields with RJSF metadata annotations
|
801
|
+
- Nested Pydantic models (inline expansion)
|
802
|
+
- List/array fields with item-level metadata
|
803
|
+
- Dictionary fields with value-type metadata
|
804
|
+
- Union types with multiple branches (anyOf)
|
805
|
+
- Optional fields (Union with None)
|
806
|
+
|
807
|
+
Args:
|
808
|
+
model_cls: A Pydantic BaseModel subclass to analyze
|
809
|
+
|
810
|
+
Returns:
|
811
|
+
A dictionary representing the RJSF uiSchema with the structure:
|
812
|
+
{
|
813
|
+
"field_name": {
|
814
|
+
"ui:widget": "text",
|
815
|
+
"ui:placeholder": "Enter value",
|
816
|
+
# ... other RJSF metadata
|
817
|
+
# For nested models, fields are inlined:
|
818
|
+
"nested_field": { ... },
|
819
|
+
# For arrays:
|
820
|
+
"items": { ... },
|
821
|
+
# For dicts:
|
822
|
+
"additionalProperties": { ... },
|
823
|
+
# For unions:
|
824
|
+
"anyOf": [{ ... }, { ... }]
|
825
|
+
}
|
826
|
+
}
|
827
|
+
|
828
|
+
Raises:
|
829
|
+
TypeError: If model_cls is not a Pydantic BaseModel subclass
|
830
|
+
"""
|
831
|
+
if not _is_pyd_model(model_cls):
|
832
|
+
raise TypeError(f"{model_cls!r} is not a Pydantic BaseModel subclass")
|
833
|
+
|
834
|
+
hints = get_type_hints(model_cls, include_extras=True)
|
835
|
+
ui: dict[str, Any] = {}
|
836
|
+
|
837
|
+
for fname, ann in hints.items():
|
838
|
+
node: dict[str, Any] = {}
|
839
|
+
|
840
|
+
base, meta = _walk_annotated_chain(ann)
|
841
|
+
base = _unwrap_optional(base)
|
842
|
+
# Start with field-level metadata (flat dict)
|
843
|
+
if meta:
|
844
|
+
node.update(meta)
|
845
|
+
|
846
|
+
origin = get_origin(base)
|
847
|
+
|
848
|
+
# Nested model -> inline children
|
849
|
+
if _is_pyd_model(base):
|
850
|
+
node.update(ui_schema_for_model(base))
|
851
|
+
|
852
|
+
# Array-like -> items
|
853
|
+
elif origin in (list, set, tuple):
|
854
|
+
(item_type, *_) = get_args(base) or (Any,)
|
855
|
+
item_base, item_meta = _walk_annotated_chain(item_type)
|
856
|
+
item_base = _unwrap_optional(item_base)
|
857
|
+
|
858
|
+
item_node: dict[str, Any] = {}
|
859
|
+
if item_meta:
|
860
|
+
item_node.update(item_meta)
|
861
|
+
if _is_pyd_model(item_base):
|
862
|
+
item_node.update(ui_schema_for_model(item_base))
|
863
|
+
node["items"] = item_node
|
864
|
+
|
865
|
+
# Dict -> additionalProperties (value side)
|
866
|
+
elif origin is dict:
|
867
|
+
key_t, val_t = get_args(base) or (Any, Any)
|
868
|
+
val_base, val_meta = _walk_annotated_chain(val_t)
|
869
|
+
val_base = _unwrap_optional(val_base)
|
870
|
+
|
871
|
+
val_node: dict[str, Any] = {}
|
872
|
+
if val_meta:
|
873
|
+
val_node.update(val_meta)
|
874
|
+
if _is_pyd_model(val_base):
|
875
|
+
val_node.update(ui_schema_for_model(val_base))
|
876
|
+
node["additionalProperties"] = val_node
|
877
|
+
|
878
|
+
# Union -> anyOf branches
|
879
|
+
elif origin is Union:
|
880
|
+
branches = []
|
881
|
+
for alt in get_args(base):
|
882
|
+
if alt in _NONE_TYPES:
|
883
|
+
continue
|
884
|
+
alt_b, alt_meta = _walk_annotated_chain(alt)
|
885
|
+
branch: dict[str, Any] = {}
|
886
|
+
if alt_meta:
|
887
|
+
branch.update(alt_meta)
|
888
|
+
if _is_pyd_model(alt_b):
|
889
|
+
branch.update(ui_schema_for_model(alt_b))
|
890
|
+
branches.append(branch)
|
891
|
+
if branches:
|
892
|
+
# Check if the metadata already has an anyOf structure (from Union composer)
|
893
|
+
if "anyOf" in node:
|
894
|
+
# Use the anyOf from the metadata (from Union composer)
|
895
|
+
pass # Keep the existing anyOf from the Union composer
|
896
|
+
else:
|
897
|
+
# Create anyOf structure for Union types
|
898
|
+
node["anyOf"] = branches
|
899
|
+
|
900
|
+
# Scalars: node already has metadata if any
|
901
|
+
|
902
|
+
ui[fname] = node
|
903
|
+
|
904
|
+
return ui
|
905
|
+
|
906
|
+
|
907
|
+
# --------- Example ---------
|
908
|
+
if __name__ == "__main__":
|
909
|
+
|
910
|
+
class Address(BaseModel):
|
911
|
+
street: Annotated[str, RJSFMetaTag.StringWidget.textfield(placeholder="Street")]
|
912
|
+
zip: Annotated[str, RJSFMetaTag.NumberWidget.updown(placeholder="12345")]
|
913
|
+
|
914
|
+
class User(BaseModel):
|
915
|
+
id: Annotated[int, RJSFMetaTag.NumberWidget.updown(disabled=True)]
|
916
|
+
name: Annotated[
|
917
|
+
str,
|
918
|
+
RJSFMetaTag.StringWidget.textfield(
|
919
|
+
placeholder="Enter your name", autofocus=True
|
920
|
+
),
|
921
|
+
]
|
922
|
+
address: Annotated[
|
923
|
+
Address, RJSFMetaTag.SpecialWidget.custom_field("AddressField")
|
924
|
+
]
|
925
|
+
tags: Annotated[
|
926
|
+
list[Annotated[str, RJSFMetaTag.ArrayWidget.checkboxes()]],
|
927
|
+
RJSFMetaTag.ArrayWidget.checkboxes(title="Tags"),
|
928
|
+
]
|
929
|
+
prefs: dict[str, Annotated[int, RJSFMetaTag.NumberWidget.range(min=0, max=100)]]
|
930
|
+
alt: Union[
|
931
|
+
Annotated[Address, RJSFMetaTag.ObjectWidget.expandable(role="home")], None
|
932
|
+
]
|
933
|
+
|
934
|
+
import json
|
935
|
+
|
936
|
+
print(json.dumps(ui_schema_for_model(User), indent=7))
|
@@ -26,15 +26,41 @@ class ContentMetadata(BaseModel):
|
|
26
26
|
|
27
27
|
class ContentChunk(BaseModel):
|
28
28
|
model_config = model_config
|
29
|
-
id: str
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
29
|
+
id: str = Field(
|
30
|
+
default="",
|
31
|
+
description="The id of the content this chunk belongs to. The id starts with 'cont_' followed by an alphanumeric string of length 24.",
|
32
|
+
examples=["cont_abcdefgehijklmnopqrstuvwx"],
|
33
|
+
)
|
34
|
+
text: str = Field(default="", description="The text content of the chunk.")
|
35
|
+
order: int = Field(
|
36
|
+
default=0,
|
37
|
+
description="The order of the chunk in the original content. Concatenating the chunks in order will give the original content.",
|
38
|
+
)
|
39
|
+
key: str | None = Field(
|
40
|
+
default=None,
|
41
|
+
description="The key of the chunk. For document chunks this is the the filename",
|
42
|
+
)
|
43
|
+
chunk_id: str | None = Field(
|
44
|
+
default=None,
|
45
|
+
description="The id of the chunk. The id starts with 'chunk_' followed by an alphanumeric string of length 24.",
|
46
|
+
examples=["chunk_abcdefgehijklmnopqrstuv"],
|
47
|
+
)
|
48
|
+
url: str | None = Field(
|
49
|
+
default=None,
|
50
|
+
description="For chunk retrieved from the web this is the url of the chunk.",
|
51
|
+
)
|
52
|
+
title: str | None = Field(
|
53
|
+
default=None,
|
54
|
+
description="The title of the chunk. For document chunks this is the title of the document.",
|
55
|
+
)
|
56
|
+
start_page: int | None = Field(
|
57
|
+
default=None,
|
58
|
+
description="The start page of the chunk. For document chunks this is the start page of the document.",
|
59
|
+
)
|
60
|
+
end_page: int | None = Field(
|
61
|
+
default=None,
|
62
|
+
description="The end page of the chunk. For document chunks this is the end page of the document.",
|
63
|
+
)
|
38
64
|
|
39
65
|
object: str | None = None
|
40
66
|
metadata: ContentMetadata | None = None
|
@@ -45,9 +71,19 @@ class ContentChunk(BaseModel):
|
|
45
71
|
|
46
72
|
class Content(BaseModel):
|
47
73
|
model_config = model_config
|
48
|
-
id: str
|
49
|
-
|
50
|
-
|
74
|
+
id: str = Field(
|
75
|
+
default="",
|
76
|
+
description="The id of the content. The id starts with 'cont_' followed by an alphanumeric string of length 24.",
|
77
|
+
examples=["cont_abcdefgehijklmnopqrstuvwx"],
|
78
|
+
)
|
79
|
+
key: str = Field(
|
80
|
+
default="",
|
81
|
+
description="The key of the content. For documents this is the the filename",
|
82
|
+
)
|
83
|
+
title: str | None = Field(
|
84
|
+
default=None,
|
85
|
+
description="The title of the content. For documents this is the title of the document.",
|
86
|
+
)
|
51
87
|
url: str | None = None
|
52
88
|
chunks: list[ContentChunk] = []
|
53
89
|
write_url: str | None = None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: unique_toolkit
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.3.1
|
4
4
|
Summary:
|
5
5
|
License: Proprietary
|
6
6
|
Author: Cedric Klinkert
|
@@ -119,6 +119,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
119
119
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
120
120
|
|
121
121
|
|
122
|
+
## [1.3.1] - 2025-09-29
|
123
|
+
- More documentation on referencing
|
124
|
+
|
125
|
+
## [1.3.0] - 2025-09-28
|
126
|
+
- Add utilitiy to enhance pydantic model with metadata
|
127
|
+
- Add capability to collect this metadata with same hierarchy as nested pydantic models
|
128
|
+
|
122
129
|
## [1.2.1] - 2025-09-28
|
123
130
|
- Fix bug where camel case arguments were not properly validated.
|
124
131
|
|
@@ -13,6 +13,7 @@ unique_toolkit/_common/endpoint_builder.py,sha256=WzJrJ7azUQhvQRd-vsFFoyj6omJpHi
|
|
13
13
|
unique_toolkit/_common/endpoint_requestor.py,sha256=JbbfJGLxgxLz8a3Yx1FdJvdHGbCYO8MSBd7cLg_Mtp0,5927
|
14
14
|
unique_toolkit/_common/exception.py,sha256=caQIE1btsQnpKCHqL2cgWUSbHup06enQu_Pt7uGUTTE,727
|
15
15
|
unique_toolkit/_common/feature_flags/schema.py,sha256=F1NdVJFNU8PKlS7bYzrIPeDu2LxRqHSM9pyw622a1Kk,547
|
16
|
+
unique_toolkit/_common/pydantic/rjsf_tags.py,sha256=T3AZIF8wny3fFov66s258nEl1GqfKevFouTtG6k9PqU,31219
|
16
17
|
unique_toolkit/_common/pydantic_helpers.py,sha256=1zzg6PlzSkHqPTdX-KoBaDHmBeeG7S5PprBsyMSCEuU,4806
|
17
18
|
unique_toolkit/_common/string_utilities.py,sha256=pbsjpnz1mwGeugebHzubzmmDtlm18B8e7xJdSvLnor0,2496
|
18
19
|
unique_toolkit/_common/token/image_token_counting.py,sha256=VpFfZyY0GIH27q_Wy4YNjk2algqvbCtJyzuuROoFQPw,2189
|
@@ -98,7 +99,7 @@ unique_toolkit/chat/utils.py,sha256=ihm-wQykBWhB4liR3LnwPVPt_qGW6ETq21Mw4HY0THE,
|
|
98
99
|
unique_toolkit/content/__init__.py,sha256=EdJg_A_7loEtCQf4cah3QARQreJx6pdz89Rm96YbMVg,940
|
99
100
|
unique_toolkit/content/constants.py,sha256=1iy4Y67xobl5VTnJB6SxSyuoBWbdLl9244xfVMUZi5o,60
|
100
101
|
unique_toolkit/content/functions.py,sha256=1zhxaJEYTvvd4qzkrbEFcrjdJxhHkfUY3dEpNfNC_hk,19052
|
101
|
-
unique_toolkit/content/schemas.py,sha256=
|
102
|
+
unique_toolkit/content/schemas.py,sha256=WB3InkKIvfWbyg9CsKFLn8Zf4zrE-YqGpCc3a0zOk7k,4774
|
102
103
|
unique_toolkit/content/service.py,sha256=ZUXJwfNdHsAw_F7cfRMDVgHpSKxiwG6Cn8p7c4hV8TM,24053
|
103
104
|
unique_toolkit/content/utils.py,sha256=qNVmHTuETaPNGqheg7TbgPr1_1jbNHDc09N5RrmUIyo,7901
|
104
105
|
unique_toolkit/embedding/__init__.py,sha256=uUyzjonPvuDCYsvXCIt7ErQXopLggpzX-MEQd3_e2kE,250
|
@@ -132,7 +133,7 @@ unique_toolkit/short_term_memory/schemas.py,sha256=OhfcXyF6ACdwIXW45sKzjtZX_gkcJ
|
|
132
133
|
unique_toolkit/short_term_memory/service.py,sha256=5PeVBu1ZCAfyDb2HLVvlmqSbyzBBuE9sI2o9Aajqjxg,8884
|
133
134
|
unique_toolkit/smart_rules/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
134
135
|
unique_toolkit/smart_rules/compile.py,sha256=cxWjb2dxEI2HGsakKdVCkSNi7VK9mr08w5sDcFCQyWI,9553
|
135
|
-
unique_toolkit-1.
|
136
|
-
unique_toolkit-1.
|
137
|
-
unique_toolkit-1.
|
138
|
-
unique_toolkit-1.
|
136
|
+
unique_toolkit-1.3.1.dist-info/LICENSE,sha256=GlN8wHNdh53xwOPg44URnwag6TEolCjoq3YD_KrWgss,193
|
137
|
+
unique_toolkit-1.3.1.dist-info/METADATA,sha256=rLo_XrmExigQ1oQ6KFaUy6EWiMmwO7uj-u7SH55nsq4,33610
|
138
|
+
unique_toolkit-1.3.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
139
|
+
unique_toolkit-1.3.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|