Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 666e12262f | |||
|
ab9646238e
|
|||
| dba492a6dc | |||
|
628abe161d
|
|||
|
136d68d273
|
|||
|
fcea994dd6
|
|||
| 39a9612106 | |||
|
27e756dadf
|
|||
|
40106e4765
|
|||
|
d418ad96ad
|
|||
| 79e65b994e | |||
|
beed4e5e97
|
|||
| b705a3a70b | |||
|
268ac85667
|
|||
|
20872d4a91
|
|||
| 34910b55d7 | |||
|
a3cbd5bc3d
|
|||
|
682f19654d
|
|||
|
4baaeed349
|
|||
|
9837a99ec9
|
|||
|
3a8ca951db
|
|||
|
57f8b571de
|
|||
|
5ec30cd565
|
|||
|
c2b9e8daf8
|
|||
|
328eb66034
|
|||
|
4de711075e
|
|||
|
abc8bc2e40
|
|||
|
10bad254d7
|
|||
| b5e2d703cb | |||
|
|
44fa0cf16a | ||
| d11e3191c3 | |||
| 2da409e6df | |||
| e775b53f7d | |||
|
f15913c58e
|
|||
|
f80a1bbda3
|
|||
| b31c990b54 | |||
|
a0d15726d4
|
|||
| 59f062ec37 | |||
|
5036059272
|
|||
|
90639b6426
|
|||
|
e43e92cb9e
|
|||
|
ffbd124cf9
|
|||
|
cfbe1f38c8
|
|||
|
9823e69329
|
|||
|
84292cf3c0
|
|||
|
8b1520741b
|
|||
|
c7e366cf08
|
|||
|
ebcc8a295e
|
|||
|
07f301db1c
|
|||
|
c9330dfd6d
|
|||
|
|
9bc16ff1aa | ||
|
|
43ce95cc9a | ||
| 81c149120e | |||
| 171dddabab | |||
|
f0192ee6d3
|
|||
|
|
82feea0ab1 | ||
| 4d5ac1c885 | |||
|
92c174c189
|
|||
| b1b5e71a81 | |||
|
156c825a67
|
|||
| b4954c3b2e | |||
|
7f44e84bce
|
|||
|
8c6a04bbdf
|
|||
|
e31002af32
|
|||
|
30290771b1
|
|||
|
f4d84d2749
|
|||
|
e61d48881f
|
|||
| f5ad857326 | |||
|
e45086e29e
|
|||
|
c1f04606ad
|
|||
|
5eb086bafd
|
|||
| 5c30e752e3 | |||
|
53418f2b2b
|
|||
| 002b75c53a | |||
|
|
1167b8a540 | ||
| 3992057c95 | |||
|
71380073e4
|
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG] Title Here"
|
||||
labels: enhancement
|
||||
labels: bug
|
||||
assignees: HideyoshiNakazone
|
||||
|
||||
---
|
||||
|
||||
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
@@ -44,6 +44,9 @@ jobs:
|
||||
uv run poe tests
|
||||
uv run poe tests-report
|
||||
|
||||
- name: Static type check
|
||||
run: uv run poe type-check
|
||||
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
|
||||
48
README.md
48
README.md
@@ -1,8 +1,8 @@
|
||||
# Jambo - JSON Schema to Pydantic Converter
|
||||
|
||||
<p align="center">
|
||||
<p style="text-align:center">
|
||||
<a href="https://github.com/HideyoshiNakazone/jambo" target="_blank">
|
||||
<img src="https://img.shields.io/github/last-commit/HideyoshiNakazone/jambo.svg">
|
||||
<img src="https://img.shields.io/github/last-commit/HideyoshiNakazone/jambo.svg" alt="Last commit">
|
||||
<img src="https://github.com/HideyoshiNakazone/jambo/actions/workflows/build.yml/badge.svg" alt="Tests">
|
||||
</a>
|
||||
<a href="https://codecov.io/gh/HideyoshiNakazone/jambo" target="_blank">
|
||||
@@ -19,12 +19,13 @@
|
||||
</p>
|
||||
|
||||
**Jambo** is a Python package that automatically converts [JSON Schema](https://json-schema.org/) definitions into [Pydantic](https://docs.pydantic.dev/) models.
|
||||
It's designed to streamline schema validation and enforce type safety using Pydantic's powerful validation features.
|
||||
It's designed to streamline schema validation and enforce type safety using Pydantic's validation features.
|
||||
|
||||
Created to simplifying the process of dynamically generating Pydantic models for AI frameworks like [LangChain](https://www.langchain.com/), [CrewAI](https://www.crewai.com/), and others.
|
||||
Created to simplify the process of dynamically generating Pydantic models for AI frameworks like [LangChain](https://www.langchain.com/), [CrewAI](https://www.crewai.com/), and others.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- ✅ Convert JSON Schema into Pydantic models dynamically;
|
||||
@@ -56,10 +57,19 @@ pip install jambo
|
||||
|
||||
## 🚀 Usage
|
||||
|
||||
There are two ways to build models with Jambo:
|
||||
|
||||
1. The original static API: `SchemaConverter.build(schema)` doesn't persist any reference cache between calls and doesn't require any configuration.
|
||||
2. The new instance API: use a `SchemaConverter()` instance and call `build_with_cache`, which exposes and persists a reference cache and helper methods.
|
||||
|
||||
The instance API is useful when you want to reuse generated subtypes, inspect cached models, or share caches between converters; all leveraging namespaces via the `$id` property in JSON Schema. See the docs for full details: https://jambo.readthedocs.io/en/latest/usage.ref_cache.html
|
||||
|
||||
|
||||
### Static (compatibility) example
|
||||
|
||||
```python
|
||||
from jambo import SchemaConverter
|
||||
|
||||
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
@@ -70,12 +80,40 @@ schema = {
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
# Old-style convenience API (kept for compatibility)
|
||||
Person = SchemaConverter.build(schema)
|
||||
|
||||
obj = Person(name="Alice", age=30)
|
||||
print(obj)
|
||||
```
|
||||
|
||||
### Instance API (recommended for cache control)
|
||||
|
||||
```python
|
||||
from jambo import SchemaConverter
|
||||
|
||||
converter = SchemaConverter()
|
||||
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {"type": "object", "properties": {"street": {"type": "string"}}},
|
||||
},
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
# build_with_cache populates the converter's instance-level ref cache
|
||||
Person = converter.build_with_cache(schema)
|
||||
|
||||
# you can retrieve cached subtypes by name/path
|
||||
cached_person = converter.get_cached_ref("Person")
|
||||
# clear the instance cache when needed
|
||||
converter.clear_ref_cache()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Example Validations
|
||||
|
||||
@@ -18,6 +18,7 @@ extensions = [
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx_autodoc_typehints", # <-- needed
|
||||
]
|
||||
|
||||
|
||||
@@ -35,3 +36,6 @@ html_static_path = ["_static"]
|
||||
# -- Options for autodoc -----------------------------------------------------
|
||||
add_module_names = False
|
||||
python_use_unqualified_type_names = True
|
||||
|
||||
|
||||
autodoc_typehints = "both"
|
||||
|
||||
37
docs/source/jambo.exceptions.rst
Normal file
37
docs/source/jambo.exceptions.rst
Normal file
@@ -0,0 +1,37 @@
|
||||
jambo.exceptions package
|
||||
========================
|
||||
|
||||
Submodules
|
||||
----------
|
||||
|
||||
jambo.exceptions.internal\_assertion\_exception module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: jambo.exceptions.internal_assertion_exception
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.exceptions.invalid\_schema\_exception module
|
||||
--------------------------------------------------
|
||||
|
||||
.. automodule:: jambo.exceptions.invalid_schema_exception
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.exceptions.unsupported\_schema\_exception module
|
||||
------------------------------------------------------
|
||||
|
||||
.. automodule:: jambo.exceptions.unsupported_schema_exception
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
Module contents
|
||||
---------------
|
||||
|
||||
.. automodule:: jambo.exceptions
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
@@ -36,6 +36,22 @@ jambo.parser.boolean\_type\_parser module
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.const\_type\_parser module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.const_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.enum\_type\_parser module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.enum_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.float\_type\_parser module
|
||||
---------------------------------------
|
||||
|
||||
@@ -52,6 +68,14 @@ jambo.parser.int\_type\_parser module
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.null\_type\_parser module
|
||||
--------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.null_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.object\_type\_parser module
|
||||
----------------------------------------
|
||||
|
||||
@@ -60,6 +84,14 @@ jambo.parser.object\_type\_parser module
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.oneof\_type\_parser module
|
||||
---------------------------------------
|
||||
|
||||
.. automodule:: jambo.parser.oneof_type_parser
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:undoc-members:
|
||||
|
||||
jambo.parser.ref\_type\_parser module
|
||||
-------------------------------------
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ Subpackages
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
jambo.exceptions
|
||||
jambo.parser
|
||||
jambo.types
|
||||
|
||||
|
||||
348
docs/source/usage.ref_cache.rst
Normal file
348
docs/source/usage.ref_cache.rst
Normal file
@@ -0,0 +1,348 @@
|
||||
===============
|
||||
Reference Cache
|
||||
===============
|
||||
|
||||
The reference cache is named after the mechanism used to implement
|
||||
the `$ref` keyword in the JSON Schema specification.
|
||||
|
||||
Internally, the cache is used by both :py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>`
|
||||
and :py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>`.
|
||||
However, only :py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>` exposes the cache through a supported API;
|
||||
:py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>` uses the cache internally and does not provide access to it.
|
||||
|
||||
The reference cache accepts a mutable mapping (typically a plain Python dict)
|
||||
that maps reference names (strings) to generated Pydantic model classes.
|
||||
Since only the reference names are stored it can cause name collisions if
|
||||
multiple schemas with overlapping names are processed using the same cache.
|
||||
Therefore, it's recommended that each namespace or schema source uses its own
|
||||
:class:`SchemaConverter` instance.
|
||||
|
||||
-----------------------------------------
|
||||
Configuring and Using the Reference Cache
|
||||
-----------------------------------------
|
||||
|
||||
The reference cache can be used in three ways:
|
||||
|
||||
* Without a persistent reference cache (no sharing between calls).
|
||||
* Passing an explicit ``ref_cache`` dictionary to a call.
|
||||
* Using the converter instance's default cache (the instance-level cache).
|
||||
|
||||
|
||||
Usage Without Reference Cache
|
||||
=============================
|
||||
|
||||
When you run the library without a persistent reference cache, the generated
|
||||
types are not stored for reuse. Each call to a build method creates fresh
|
||||
Pydantic model classes (they will have different Python object identities).
|
||||
Because nothing is cached, you cannot look up generated subtypes later.
|
||||
|
||||
This is the default behaviour of :py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>`.
|
||||
You can achieve the same behaviour with :py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>` by
|
||||
passing ``without_cache=True``.
|
||||
|
||||
|
||||
Usage: Manually Passing a Reference Cache
|
||||
=========================================
|
||||
|
||||
You can create and pass your own mutable mapping (typically a plain dict)
|
||||
as the reference cache. This gives you full control over sharing and
|
||||
lifetime of cached types. When two converters share the same dict, types
|
||||
created by one converter will be reused by the other.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
# a shared cache you control
|
||||
shared_cache = {}
|
||||
|
||||
converter1 = SchemaConverter(shared_cache)
|
||||
converter2 = SchemaConverter(shared_cache)
|
||||
|
||||
model1 = converter1.build_with_cache(schema)
|
||||
model2 = converter2.build_with_cache(schema)
|
||||
|
||||
# Because both converters use the same cache object, the built models are the same object
|
||||
assert model1 is model2
|
||||
|
||||
If you prefer a per-call cache (leaving the converter's instance cache unchanged), pass the ``ref_cache`` parameter to
|
||||
:py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# pass an explicit, private cache for this call only
|
||||
model_a = converter1.build_with_cache(schema, ref_cache={})
|
||||
model_b = converter1.build_with_cache(schema, ref_cache={})
|
||||
|
||||
# because each call received a fresh dict, the resulting model classes are distinct
|
||||
assert model_a is not model_b
|
||||
|
||||
|
||||
Usage: Using the Instance Default (Instance-level) Cache
|
||||
=======================================================
|
||||
|
||||
By default, a :class:`SchemaConverter` instance creates and keeps an internal
|
||||
reference cache (a plain dict). Reusing the same converter instance across
|
||||
multiple calls will reuse that cache and therefore reuse previously generated
|
||||
model classes.
|
||||
|
||||
That cache is isolated per namespace via the `$id` property in JSON Schema, so
|
||||
schemas with different `$id` values will not collide in the same cache.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
# no $id in this example, therefore a default namespace is used
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
},
|
||||
"required": ["name", "address"],
|
||||
}
|
||||
|
||||
converter = SchemaConverter() # has its own internal cache
|
||||
|
||||
model1 = converter.build_with_cache(schema)
|
||||
model2 = converter.build_with_cache(schema)
|
||||
|
||||
# model1 and model2 are the same object because the instance cache persisted
|
||||
assert model1 is model2
|
||||
|
||||
When passing a schema with a different `$id`, the instance cache keeps types
|
||||
separate:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
schema_a = {
|
||||
"$id": "namespace_a",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
schema_b = {
|
||||
"$id": "namespace_b",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
converter = SchemaConverter() # has its own internal cache
|
||||
|
||||
model_a = converter.build_with_cache(schema_a)
|
||||
model_b = converter.build_with_cache(schema_b)
|
||||
|
||||
# different $id values isolate the types in the same cache
|
||||
assert model_a is not model_b
|
||||
|
||||
If you want to temporarily avoid using the instance cache for a single call,
|
||||
use ``without_cache=True``. That causes :py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>` to
|
||||
use a fresh, empty cache for the duration of that call only:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
model1 = converter.build_with_cache(schema, without_cache=True)
|
||||
model2 = converter.build_with_cache(schema, without_cache=True)
|
||||
|
||||
# each call used a fresh cache, so the models are distinct
|
||||
assert model1 is not model2
|
||||
|
||||
|
||||
Inspecting and Managing the Cache
|
||||
=================================
|
||||
|
||||
The converter provides a small, explicit API to inspect and manage the
|
||||
instance cache.
|
||||
|
||||
Retrieving cached types
|
||||
-----------------------
|
||||
|
||||
:py:meth:`SchemaConverter.get_cached_ref <jambo.SchemaConverter.get_cached_ref>`(name, namespace="default") — returns a cached model class or ``None``.
|
||||
|
||||
Retrieving the root type of the schema
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When retrieving the root type of a schema, pass the schema's ``title`` property as the name.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
converter = SchemaConverter()
|
||||
|
||||
schema = {
|
||||
"title": "person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
}
|
||||
|
||||
person_model = converter.build_with_cache(schema)
|
||||
cached_person_model = converter.get_cached_ref("person")
|
||||
|
||||
|
||||
Retrieving a subtype
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When retrieving a subtype, pass a path string (for example, ``parent_name.field_name``) as the name.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
converter = SchemaConverter()
|
||||
|
||||
schema = {
|
||||
"title": "person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
person_model = converter.build_with_cache(schema)
|
||||
cached_address_model = converter.get_cached_ref("person.address")
|
||||
|
||||
|
||||
|
||||
Retrieving a type from ``$defs``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When retrieving a type defined in ``$defs``, access it directly by its name.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
converter = SchemaConverter()
|
||||
|
||||
schema = {
|
||||
"title": "person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {"$ref": "#/$defs/address"},
|
||||
},
|
||||
"$defs": {
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
person_model = converter.build_with_cache(schema)
|
||||
cached_address_model = converter.get_cached_ref("address")
|
||||
|
||||
|
||||
Isolation by Namespace
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The instance cache is isolated per namespace via the `$id` property in JSON Schema.
|
||||
When retrieving a cached type, you can specify the namespace to look in
|
||||
(via the ``namespace`` parameter). By default, the ``default`` namespace is used
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
converter = SchemaConverter()
|
||||
|
||||
schema_a = {
|
||||
"$id": "namespace_a",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
schema_b = {
|
||||
"$id": "namespace_b",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
},
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
person_a = converter.build_with_cache(schema_a)
|
||||
person_b = converter.build_with_cache(schema_b)
|
||||
|
||||
cached_person_a = converter.get_cached_ref("Person", namespace="namespace_a")
|
||||
cached_person_b = converter.get_cached_ref("Person", namespace="namespace_b")
|
||||
|
||||
assert cached_person_a is person_a
|
||||
assert cached_person_b is person_b
|
||||
|
||||
|
||||
Clearing the cache
|
||||
------------------
|
||||
|
||||
:py:meth:`SchemaConverter.clear_ref_cache <jambo.SchemaConverter.clear_ref_cache>`(namespace: Optional[str]="default") — removes all entries from the instance cache.
|
||||
|
||||
|
||||
When you want to clear the instance cache, use :py:meth:`SchemaConverter.clear_ref_cache <jambo.SchemaConverter.clear_ref_cache>`.
|
||||
You can optionally specify a ``namespace`` to clear only that namespace;
|
||||
otherwise, the default namespace is cleared.
|
||||
|
||||
If you want to clear all namespaces, call :py:meth:`SchemaConverter.clear_ref_cache <jambo.SchemaConverter.clear_ref_cache>` passing `None` as the namespace,
|
||||
which removes all entries from all namespaces.
|
||||
|
||||
|
||||
Notes and Behavioural Differences
|
||||
================================
|
||||
|
||||
* :py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>` does not expose or persist an instance cache. If you call it without
|
||||
providing a ``ref_cache`` it will create and use a temporary cache for that
|
||||
call only; nothing from that call will be available later via
|
||||
:py:meth:`SchemaConverter.get_cached_ref <jambo.SchemaConverter.get_cached_ref>`.
|
||||
|
||||
* :py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>` is the supported entry point when you want
|
||||
cache control: it uses the instance cache by default, accepts an explicit
|
||||
``ref_cache`` dict for per-call control, or uses ``without_cache=True`` to
|
||||
run with an ephemeral cache.
|
||||
|
||||
|
||||
References in the Test Suite
|
||||
============================
|
||||
|
||||
These behaviours are exercised in the project's tests; see :mod:`tests.test_schema_converter`
|
||||
for examples and additional usage notes.
|
||||
@@ -1,9 +1,15 @@
|
||||
===================
|
||||
Using Jambo
|
||||
===================
|
||||
|
||||
Jambo is designed to be easy to use, it doesn't require any complex setup or configuration.
|
||||
Below a example of how to use Jambo to convert a JSON Schema into a Pydantic model.
|
||||
Jambo is designed to be easy to use. It doesn't require complex setup or configuration when not needed, while providing more powerful instance methods when you do need control.
|
||||
|
||||
Below is an example of how to use Jambo to convert a JSON Schema into a Pydantic model.
|
||||
|
||||
|
||||
-------------------------
|
||||
Static Method (no config)
|
||||
-------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -15,8 +21,16 @@ Below a example of how to use Jambo to convert a JSON Schema into a Pydantic mod
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
},
|
||||
"required": ["name"],
|
||||
"required": ["name", "address"],
|
||||
}
|
||||
|
||||
Person = SchemaConverter.build(schema)
|
||||
@@ -26,16 +40,81 @@ Below a example of how to use Jambo to convert a JSON Schema into a Pydantic mod
|
||||
# Output: Person(name='Alice', age=30)
|
||||
|
||||
|
||||
The :py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>` static method takes a JSON Schema dictionary and returns a Pydantic model class. You can then instantiate this class with the required fields, and it will automatically validate the data according to the schema.
|
||||
The :py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>` static method takes a JSON Schema dictionary and returns a Pydantic model class.
|
||||
|
||||
If passed a description inside the schema it will also add it to the Pydantic model using the `description` field. This is useful for AI Frameworks as: LangChain, CrewAI and others, as they use this description for passing context to LLMs.
|
||||
Note: the static ``build`` method was the original public API of this library. It creates and returns a model class for the provided schema but does not expose or persist an instance cache.
|
||||
|
||||
|
||||
For more complex schemas and types see our documentation on
|
||||
--------------------------------
|
||||
Instance Method (with ref cache)
|
||||
--------------------------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from jambo import SchemaConverter
|
||||
|
||||
converter = SchemaConverter()
|
||||
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
},
|
||||
"required": ["name", "address"],
|
||||
}
|
||||
|
||||
# The instance API (build_with_cache) populates the converter's instance-level reference cache
|
||||
Person = converter.build_with_cache(schema)
|
||||
|
||||
obj = Person(name="Alice", age=30)
|
||||
print(obj)
|
||||
# Output: Person(name='Alice', age=30)
|
||||
|
||||
# When using the converter's built-in instance cache (no ref_cache passed to the call),
|
||||
# all object types parsed during the build are stored and can be retrieved via get_cached_ref.
|
||||
|
||||
cached_person_model = converter.get_cached_ref("Person")
|
||||
assert Person is cached_person_model # the cached class is the same object that was built
|
||||
|
||||
# A nested/subobject type can also be retrieved from the instance cache
|
||||
cached_address_model = converter.get_cached_ref("Person.address")
|
||||
|
||||
|
||||
The :py:meth:`SchemaConverter.build_with_cache <jambo.SchemaConverter.build_with_cache>` instance method was added after the
|
||||
initial static API to make it easier to access and reuse subtypes defined in a schema.
|
||||
Unlike the original static :py:meth:`SchemaConverter.build <jambo.SchemaConverter.build>`,
|
||||
the instance method persists and exposes the reference cache and provides helpers such as
|
||||
:py:meth:`SchemaConverter.get_cached_ref <jambo.SchemaConverter.get_cached_ref>` and
|
||||
:py:meth:`SchemaConverter.clear_ref_cache <jambo.SchemaConverter.clear_ref_cache>`.
|
||||
|
||||
.. warning::
|
||||
The instance API with reference cache can lead to schema and type name collisions if not managed carefully.
|
||||
It's recommended that each schema defines its own unique namespace using the `$id` property in JSON Schema,
|
||||
and then access it's ref_cache by passing it explicitly when needed.
|
||||
|
||||
For details and examples about the reference cache and the different cache modes (instance cache, per-call cache, ephemeral cache), see:
|
||||
|
||||
.. toctree::
|
||||
usage.ref_cache
|
||||
|
||||
|
||||
Type System
|
||||
-----------
|
||||
|
||||
For a full explanation of the supported schemas and types see our documentation on types:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
usage.string
|
||||
usage.numeric
|
||||
|
||||
10
jambo/exceptions/__init__.py
Normal file
10
jambo/exceptions/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from .internal_assertion_exception import InternalAssertionException
|
||||
from .invalid_schema_exception import InvalidSchemaException
|
||||
from .unsupported_schema_exception import UnsupportedSchemaException
|
||||
|
||||
|
||||
__all__ = [
|
||||
"InternalAssertionException",
|
||||
"InvalidSchemaException",
|
||||
"UnsupportedSchemaException",
|
||||
]
|
||||
16
jambo/exceptions/internal_assertion_exception.py
Normal file
16
jambo/exceptions/internal_assertion_exception.py
Normal file
@@ -0,0 +1,16 @@
|
||||
class InternalAssertionException(RuntimeError):
|
||||
"""Exception raised for internal assertions."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
) -> None:
|
||||
# Normalize message by stripping redundant prefix if present
|
||||
message = message.removeprefix("Internal Assertion Failed: ")
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
f"Internal Assertion Failed: {super().__str__()}\n"
|
||||
"This is likely a bug in Jambo. Please report it at"
|
||||
)
|
||||
27
jambo/exceptions/invalid_schema_exception.py
Normal file
27
jambo/exceptions/invalid_schema_exception.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from typing_extensions import Optional
|
||||
|
||||
|
||||
class InvalidSchemaException(ValueError):
|
||||
"""Exception raised for invalid JSON schemas."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
invalid_field: Optional[str] = None,
|
||||
cause: Optional[BaseException] = None,
|
||||
) -> None:
|
||||
# Normalize message by stripping redundant prefix if present
|
||||
message = message.removeprefix("Invalid JSON Schema: ")
|
||||
self.invalid_field = invalid_field
|
||||
self.cause = cause
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
base_msg = f"Invalid JSON Schema: {super().__str__()}"
|
||||
if self.invalid_field:
|
||||
return f"{base_msg} (invalid field: {self.invalid_field})"
|
||||
if self.cause:
|
||||
return (
|
||||
f"{base_msg} (caused by {self.cause.__class__.__name__}: {self.cause})"
|
||||
)
|
||||
return base_msg
|
||||
23
jambo/exceptions/unsupported_schema_exception.py
Normal file
23
jambo/exceptions/unsupported_schema_exception.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from typing_extensions import Optional
|
||||
|
||||
|
||||
class UnsupportedSchemaException(ValueError):
|
||||
"""Exception raised for unsupported JSON schemas."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
message: str,
|
||||
unsupported_field: Optional[str] = None,
|
||||
cause: Optional[BaseException] = None,
|
||||
) -> None:
|
||||
# Normalize message by stripping redundant prefix if present
|
||||
message = message.removeprefix("Unsupported JSON Schema: ")
|
||||
self.unsupported_field = unsupported_field
|
||||
self.cause = cause
|
||||
super().__init__(message)
|
||||
|
||||
def __str__(self) -> str:
|
||||
base_msg = f"Unsupported JSON Schema: {super().__str__()}"
|
||||
if self.unsupported_field:
|
||||
return f"{base_msg} (unsupported field: {self.unsupported_field})"
|
||||
return base_msg
|
||||
@@ -29,4 +29,4 @@ __all__ = [
|
||||
"OneOfTypeParser",
|
||||
"StringTypeParser",
|
||||
"RefTypeParser",
|
||||
]
|
||||
]
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.types.type_parser_options import JSONSchema, TypeParserOptions
|
||||
|
||||
from pydantic import Field, TypeAdapter
|
||||
from typing_extensions import Annotated, Any, Generic, Self, TypeVar, Unpack
|
||||
from typing_extensions import Annotated, Any, ClassVar, Generic, Self, TypeVar, Unpack
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar("T", bound=type)
|
||||
|
||||
|
||||
class GenericTypeParser(ABC, Generic[T]):
|
||||
json_schema_type: str = None
|
||||
json_schema_type: ClassVar[str]
|
||||
|
||||
type_mappings: dict[str, str] = {}
|
||||
|
||||
default_mappings = {
|
||||
"default": "default",
|
||||
"description": "description",
|
||||
"examples": "examples",
|
||||
"title": "title",
|
||||
"deprecated": "deprecated",
|
||||
}
|
||||
|
||||
@abstractmethod
|
||||
def from_properties_impl(
|
||||
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[T, dict]:
|
||||
"""
|
||||
Abstract method to convert properties to a type and its fields properties.
|
||||
@@ -32,7 +36,7 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
"""
|
||||
|
||||
def from_properties(
|
||||
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[T, dict]:
|
||||
"""
|
||||
Converts properties to a type and its fields properties.
|
||||
@@ -46,15 +50,20 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
)
|
||||
|
||||
if not self._validate_default(parsed_type, parsed_properties):
|
||||
raise ValueError(
|
||||
f"Default value {properties.get('default')} is not valid for type {parsed_type.__name__}"
|
||||
raise InvalidSchemaException(
|
||||
"Default value is not valid", invalid_field=name
|
||||
)
|
||||
|
||||
if not self._validate_examples(parsed_type, parsed_properties):
|
||||
raise InvalidSchemaException(
|
||||
"Examples values are not valid", invalid_field=name
|
||||
)
|
||||
|
||||
return parsed_type, parsed_properties
|
||||
|
||||
@classmethod
|
||||
def type_from_properties(
|
||||
cls, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
cls, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[type, dict]:
|
||||
"""
|
||||
Factory method to fetch the appropriate type parser based on properties
|
||||
@@ -64,22 +73,53 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
:param kwargs: Additional options for type parsing.
|
||||
:return: A tuple containing the type and its properties.
|
||||
"""
|
||||
parser = cls._get_impl(properties)
|
||||
|
||||
parser = cls._get_impl(cls._normalize_properties(properties))
|
||||
|
||||
return parser().from_properties(name=name, properties=properties, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _normalize_properties(properties: JSONSchema) -> JSONSchema:
|
||||
"""
|
||||
Normalizes the properties dictionary to ensure consistent structure.
|
||||
:param properties: The properties to be normalized.
|
||||
"""
|
||||
type_value = properties.pop("type", None)
|
||||
|
||||
if isinstance(type_value, str):
|
||||
properties["type"] = type_value
|
||||
return properties
|
||||
|
||||
if isinstance(type_value, list) and len(type_value) == 0:
|
||||
raise InvalidSchemaException(
|
||||
"Invalid schema: 'type' list cannot be empty",
|
||||
invalid_field=str(properties),
|
||||
)
|
||||
|
||||
if isinstance(type_value, list) and len(type_value) == 1:
|
||||
properties["type"] = type_value[0]
|
||||
return properties
|
||||
|
||||
if isinstance(type_value, list):
|
||||
properties["anyOf"] = [{"type": t} for t in type_value]
|
||||
return properties
|
||||
|
||||
return properties
|
||||
|
||||
@classmethod
|
||||
def _get_impl(cls, properties: dict[str, Any]) -> type[Self]:
|
||||
def _get_impl(cls, properties: JSONSchema) -> type[Self]:
|
||||
for subcls in cls.__subclasses__():
|
||||
schema_type, schema_value = subcls._get_schema_type()
|
||||
|
||||
if schema_type not in properties:
|
||||
continue
|
||||
|
||||
if schema_value is None or schema_value == properties[schema_type]:
|
||||
if schema_value is None or schema_value == properties[schema_type]: # type: ignore
|
||||
return subcls
|
||||
|
||||
raise ValueError("Unknown type")
|
||||
raise InvalidSchemaException(
|
||||
"No suitable type parser found", invalid_field=str(properties)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _get_schema_type(cls) -> tuple[str, str | None]:
|
||||
@@ -108,7 +148,7 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _validate_default(field_type: type, field_prop: dict) -> bool:
|
||||
def _validate_default(field_type: T, field_prop: dict) -> bool:
|
||||
value = field_prop.get("default")
|
||||
|
||||
if value is None and field_prop.get("default_factory") is not None:
|
||||
@@ -117,8 +157,27 @@ class GenericTypeParser(ABC, Generic[T]):
|
||||
if value is None:
|
||||
return True
|
||||
|
||||
return GenericTypeParser._is_valid_value(field_type, field_prop, value)
|
||||
|
||||
@staticmethod
|
||||
def _validate_examples(field_type: T, field_prop: dict) -> bool:
|
||||
examples = field_prop.get("examples")
|
||||
|
||||
if examples is None:
|
||||
return True
|
||||
|
||||
if not isinstance(examples, list):
|
||||
return False
|
||||
|
||||
return all(
|
||||
GenericTypeParser._is_valid_value(field_type, field_prop, e)
|
||||
for e in examples
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _is_valid_value(field_type: T, field_prop: dict, value: Any) -> bool:
|
||||
try:
|
||||
field = Annotated[field_type, Field(**field_prop)]
|
||||
field = Annotated[field_type, Field(**field_prop)] # type: ignore
|
||||
TypeAdapter(field).validate_python(value)
|
||||
except Exception as _:
|
||||
return False
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
from typing_extensions import Any, Unpack
|
||||
from typing_extensions import Unpack
|
||||
|
||||
|
||||
class AllOfTypeParser(GenericTypeParser):
|
||||
@@ -10,7 +12,7 @@ class AllOfTypeParser(GenericTypeParser):
|
||||
json_schema_type = "allOf"
|
||||
|
||||
def from_properties_impl(
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
sub_properties = properties.get("allOf", [])
|
||||
|
||||
@@ -25,36 +27,46 @@ class AllOfTypeParser(GenericTypeParser):
|
||||
sub_properties
|
||||
)
|
||||
|
||||
if (examples := properties.get("examples")) is not None:
|
||||
combined_properties["examples"] = examples
|
||||
|
||||
return parser().from_properties_impl(name, combined_properties, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _get_type_parser(
|
||||
sub_properties: list[dict[str, Any]],
|
||||
sub_properties: list[JSONSchema],
|
||||
) -> type[GenericTypeParser]:
|
||||
if not sub_properties:
|
||||
raise ValueError("Invalid JSON Schema: 'allOf' is empty.")
|
||||
raise InvalidSchemaException(
|
||||
"'allOf' must contain at least one schema", invalid_field="allOf"
|
||||
)
|
||||
|
||||
parsers = set(
|
||||
parsers: set[type[GenericTypeParser]] = set(
|
||||
GenericTypeParser._get_impl(sub_property) for sub_property in sub_properties
|
||||
)
|
||||
if len(parsers) != 1:
|
||||
raise ValueError("Invalid JSON Schema: allOf types do not match.")
|
||||
raise InvalidSchemaException(
|
||||
"All sub-schemas in 'allOf' must resolve to the same type",
|
||||
invalid_field="allOf",
|
||||
)
|
||||
|
||||
return parsers.pop()
|
||||
|
||||
@staticmethod
|
||||
def _rebuild_properties_from_subproperties(
|
||||
sub_properties: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
properties = {}
|
||||
sub_properties: list[JSONSchema],
|
||||
) -> JSONSchema:
|
||||
properties: JSONSchema = {}
|
||||
for subProperty in sub_properties:
|
||||
for name, prop in subProperty.items():
|
||||
if name not in properties:
|
||||
properties[name] = prop
|
||||
properties[name] = prop # type: ignore
|
||||
else:
|
||||
# Merge properties if they exist in both sub-properties
|
||||
properties[name] = AllOfTypeParser._validate_prop(
|
||||
name, properties[name], prop
|
||||
properties[name] = AllOfTypeParser._validate_prop( # type: ignore
|
||||
name,
|
||||
properties[name], # type: ignore
|
||||
prop,
|
||||
)
|
||||
return properties
|
||||
|
||||
@@ -65,8 +77,8 @@ class AllOfTypeParser(GenericTypeParser):
|
||||
|
||||
if prop_name == "default":
|
||||
if old_value != new_value:
|
||||
raise ValueError(
|
||||
f"Invalid JSON Schema: conflicting defaults for '{prop_name}'"
|
||||
raise InvalidSchemaException(
|
||||
f"Conflicting defaults for '{prop_name}'", invalid_field=prop_name
|
||||
)
|
||||
return old_value
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -14,18 +15,25 @@ class AnyOfTypeParser(GenericTypeParser):
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "anyOf" not in properties:
|
||||
raise ValueError(f"Invalid JSON Schema: {properties}")
|
||||
raise InvalidSchemaException(
|
||||
f"AnyOf type {name} must have 'anyOf' property defined.",
|
||||
invalid_field="anyOf",
|
||||
)
|
||||
|
||||
if not isinstance(properties["anyOf"], list):
|
||||
raise ValueError(f"Invalid JSON Schema: {properties['anyOf']}")
|
||||
raise InvalidSchemaException(
|
||||
"AnyOf must be a list of types.", invalid_field="anyOf"
|
||||
)
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
sub_properties = properties["anyOf"]
|
||||
|
||||
sub_types = [
|
||||
GenericTypeParser.type_from_properties(name, subProperty, **kwargs)
|
||||
for subProperty in sub_properties
|
||||
GenericTypeParser.type_from_properties(
|
||||
f"{name}.sub{i}", subProperty, **kwargs
|
||||
)
|
||||
for i, subProperty in enumerate(sub_properties)
|
||||
]
|
||||
|
||||
if not kwargs.get("required", False):
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
from typing_extensions import Iterable, TypeVar, Unpack
|
||||
from typing_extensions import (
|
||||
Iterable,
|
||||
Unpack,
|
||||
)
|
||||
|
||||
import copy
|
||||
|
||||
|
||||
V = TypeVar("V")
|
||||
|
||||
|
||||
class ArrayTypeParser(GenericTypeParser):
|
||||
mapped_type = list
|
||||
|
||||
json_schema_type = "type:array"
|
||||
|
||||
default_mappings = {"description": "description"}
|
||||
|
||||
type_mappings = {
|
||||
"maxItems": "max_length",
|
||||
"minItems": "min_length",
|
||||
@@ -26,8 +25,15 @@ class ArrayTypeParser(GenericTypeParser):
|
||||
):
|
||||
item_properties = kwargs.copy()
|
||||
item_properties["required"] = True
|
||||
|
||||
if (items := properties.get("items")) is None:
|
||||
raise InvalidSchemaException(
|
||||
f"Array type {name} must have 'items' property defined.",
|
||||
invalid_field="items",
|
||||
)
|
||||
|
||||
_item_type, _item_args = GenericTypeParser.type_from_properties(
|
||||
name, properties["items"], **item_properties
|
||||
name, items, **item_properties
|
||||
)
|
||||
|
||||
wrapper_type = set if properties.get("uniqueItems", False) else list
|
||||
@@ -35,11 +41,18 @@ class ArrayTypeParser(GenericTypeParser):
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
if "default" in properties or not kwargs.get("required", False):
|
||||
if (
|
||||
default_value := mapped_properties.pop("default", None)
|
||||
) is not None or not kwargs.get("required", False):
|
||||
mapped_properties["default_factory"] = self._build_default_factory(
|
||||
properties.get("default"), wrapper_type
|
||||
default_value, wrapper_type
|
||||
)
|
||||
|
||||
if (example_values := mapped_properties.pop("examples", None)) is not None:
|
||||
mapped_properties["examples"] = [
|
||||
wrapper_type(example) for example in example_values
|
||||
]
|
||||
|
||||
return field_type, mapped_properties
|
||||
|
||||
def _build_default_factory(self, default_list, wrapper_type):
|
||||
@@ -47,8 +60,9 @@ class ArrayTypeParser(GenericTypeParser):
|
||||
return lambda: None
|
||||
|
||||
if not isinstance(default_list, Iterable):
|
||||
raise ValueError(
|
||||
f"Default value for array must be an iterable, got {type(default_list)}"
|
||||
raise InvalidSchemaException(
|
||||
f"Default value for array must be an iterable, got {type(default_list)}",
|
||||
invalid_field="default",
|
||||
)
|
||||
|
||||
return lambda: copy.deepcopy(wrapper_type(default_list))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -20,6 +21,9 @@ class BooleanTypeParser(GenericTypeParser):
|
||||
|
||||
default_value = properties.get("default")
|
||||
if default_value is not None and not isinstance(default_value, bool):
|
||||
raise ValueError(f"Default value for {name} must be a boolean.")
|
||||
raise InvalidSchemaException(
|
||||
f"Default value for {name} must be a boolean.",
|
||||
invalid_field="default",
|
||||
)
|
||||
|
||||
return bool, mapped_properties
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
@@ -12,19 +13,24 @@ class ConstTypeParser(GenericTypeParser):
|
||||
default_mappings = {
|
||||
"const": "default",
|
||||
"description": "description",
|
||||
"examples": "examples",
|
||||
}
|
||||
|
||||
def from_properties_impl(
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "const" not in properties:
|
||||
raise ValueError(f"Const type {name} must have 'const' property defined.")
|
||||
raise InvalidSchemaException(
|
||||
f"Const type {name} must have 'const' property defined.",
|
||||
invalid_field="const",
|
||||
)
|
||||
|
||||
const_value = properties["const"]
|
||||
|
||||
if not isinstance(const_value, JSONSchemaNativeTypes):
|
||||
raise ValueError(
|
||||
f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}."
|
||||
raise InvalidSchemaException(
|
||||
f"Const type {name} must have 'const' value of allowed types: {JSONSchemaNativeTypes}.",
|
||||
invalid_field="const",
|
||||
)
|
||||
|
||||
const_type = self._build_const_type(const_value)
|
||||
@@ -48,4 +54,4 @@ class ConstTypeParser(GenericTypeParser):
|
||||
)
|
||||
return value
|
||||
|
||||
return Annotated[type(const_value), AfterValidator(_validate_const_value)]
|
||||
return Annotated[type(const_value), AfterValidator(_validate_const_value)]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchemaNativeTypes
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
from jambo.types.type_parser_options import JSONSchema, TypeParserOptions
|
||||
|
||||
from typing_extensions import Unpack
|
||||
|
||||
@@ -11,30 +12,38 @@ class EnumTypeParser(GenericTypeParser):
|
||||
json_schema_type = "enum"
|
||||
|
||||
def from_properties_impl(
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "enum" not in properties:
|
||||
raise ValueError(f"Enum type {name} must have 'enum' property defined.")
|
||||
raise InvalidSchemaException(
|
||||
f"Enum type {name} must have 'enum' property defined.",
|
||||
invalid_field="enum",
|
||||
)
|
||||
|
||||
enum_values = properties["enum"]
|
||||
|
||||
if not isinstance(enum_values, list):
|
||||
raise ValueError(f"Enum type {name} must have 'enum' as a list of values.")
|
||||
raise InvalidSchemaException(
|
||||
f"Enum type {name} must have 'enum' as a list of values.",
|
||||
invalid_field="enum",
|
||||
)
|
||||
|
||||
if any(
|
||||
not isinstance(value, JSONSchemaNativeTypes) for value in enum_values
|
||||
):
|
||||
raise ValueError(
|
||||
f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}."
|
||||
if any(not isinstance(value, JSONSchemaNativeTypes) for value in enum_values):
|
||||
raise InvalidSchemaException(
|
||||
f"Enum type {name} must have 'enum' values of allowed types: {JSONSchemaNativeTypes}.",
|
||||
invalid_field="enum",
|
||||
)
|
||||
|
||||
# Create a new Enum type dynamically
|
||||
enum_type = Enum(name, {str(value).upper(): value for value in enum_values})
|
||||
enum_type = Enum(name, {str(value).upper(): value for value in enum_values}) # type: ignore
|
||||
parsed_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
if (
|
||||
"default" in parsed_properties and parsed_properties["default"] is not None
|
||||
):
|
||||
if "default" in parsed_properties and parsed_properties["default"] is not None:
|
||||
parsed_properties["default"] = enum_type(parsed_properties["default"])
|
||||
|
||||
if "examples" in parsed_properties:
|
||||
parsed_properties["examples"] = [
|
||||
enum_type(example) for example in parsed_properties["examples"]
|
||||
]
|
||||
|
||||
return enum_type, parsed_properties
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
from jambo.exceptions import InternalAssertionException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, create_model
|
||||
from typing_extensions import Any, Unpack
|
||||
from pydantic.fields import FieldInfo
|
||||
from typing_extensions import Unpack
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
class ObjectTypeParser(GenericTypeParser):
|
||||
@@ -11,7 +16,7 @@ class ObjectTypeParser(GenericTypeParser):
|
||||
json_schema_type = "type:object"
|
||||
|
||||
def from_properties_impl(
|
||||
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[type[BaseModel], dict]:
|
||||
type_parsing = self.to_model(
|
||||
name,
|
||||
@@ -19,52 +24,81 @@ class ObjectTypeParser(GenericTypeParser):
|
||||
properties.get("required", []),
|
||||
**kwargs,
|
||||
)
|
||||
type_properties = {}
|
||||
type_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
if "default" in properties:
|
||||
type_properties["default_factory"] = lambda: type_parsing.model_validate(
|
||||
properties["default"]
|
||||
if (
|
||||
default_value := type_properties.pop("default", None)
|
||||
) is not None or not kwargs.get("required", False):
|
||||
type_properties["default_factory"] = (
|
||||
lambda: type_parsing.model_validate(default_value)
|
||||
if default_value is not None
|
||||
else None
|
||||
)
|
||||
|
||||
if (example_values := type_properties.pop("examples", None)) is not None:
|
||||
type_properties["examples"] = [
|
||||
type_parsing.model_validate(example) for example in example_values
|
||||
]
|
||||
|
||||
return type_parsing, type_properties
|
||||
|
||||
@classmethod
|
||||
def to_model(
|
||||
cls,
|
||||
name: str,
|
||||
schema: dict[str, Any],
|
||||
properties: dict[str, JSONSchema],
|
||||
required_keys: list[str],
|
||||
**kwargs: Unpack[TypeParserOptions],
|
||||
) -> type[BaseModel]:
|
||||
"""
|
||||
Converts JSON Schema object properties to a Pydantic model.
|
||||
:param name: The name of the model.
|
||||
:param schema: The properties of the JSON Schema object.
|
||||
:param properties: The properties of the JSON Schema object.
|
||||
:param required_keys: List of required keys in the schema.
|
||||
:return: A Pydantic model class.
|
||||
"""
|
||||
model_config = ConfigDict(validate_assignment=True)
|
||||
fields = cls._parse_properties(schema, required_keys, **kwargs)
|
||||
ref_cache = kwargs.get("ref_cache")
|
||||
if ref_cache is None:
|
||||
raise InternalAssertionException(
|
||||
"`ref_cache` must be provided in kwargs for ObjectTypeParser"
|
||||
)
|
||||
|
||||
return create_model(name, __config__=model_config, **fields)
|
||||
if (model := ref_cache.get(name)) is not None and isinstance(model, type):
|
||||
warnings.warn(
|
||||
f"Type '{name}' is already in the ref_cache and therefore cached value will be used."
|
||||
" This may indicate a namming collision in the schema or just a normal optimization,"
|
||||
" if this behavior is desired pass a clean ref_cache or use the param `without_cache`"
|
||||
)
|
||||
return model
|
||||
|
||||
model_config = ConfigDict(validate_assignment=True)
|
||||
fields = cls._parse_properties(name, properties, required_keys, **kwargs)
|
||||
|
||||
model = create_model(name, __config__=model_config, **fields) # type: ignore
|
||||
ref_cache[name] = model
|
||||
|
||||
return model
|
||||
|
||||
@classmethod
|
||||
def _parse_properties(
|
||||
cls,
|
||||
properties: dict[str, Any],
|
||||
name: str,
|
||||
properties: dict[str, JSONSchema],
|
||||
required_keys: list[str],
|
||||
**kwargs: Unpack[TypeParserOptions],
|
||||
) -> dict[str, tuple[type, Field]]:
|
||||
) -> dict[str, tuple[type, FieldInfo]]:
|
||||
required_keys = required_keys or []
|
||||
|
||||
fields = {}
|
||||
for name, prop in properties.items():
|
||||
for field_name, field_prop in properties.items():
|
||||
sub_property: TypeParserOptions = kwargs.copy()
|
||||
sub_property["required"] = name in required_keys
|
||||
sub_property["required"] = field_name in required_keys
|
||||
|
||||
parsed_type, parsed_properties = GenericTypeParser.type_from_properties(
|
||||
name, prop, **sub_property
|
||||
f"{name}.{field_name}",
|
||||
field_prop,
|
||||
**sub_property, # type: ignore
|
||||
)
|
||||
fields[name] = (parsed_type, Field(**parsed_properties))
|
||||
fields[field_name] = (parsed_type, Field(**parsed_properties))
|
||||
|
||||
return fields
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
@@ -5,6 +6,9 @@ from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter, ValidationE
|
||||
from typing_extensions import Annotated, Any, Union, Unpack, get_args
|
||||
|
||||
|
||||
Annotation = Annotated[Any, ...]
|
||||
|
||||
|
||||
class OneOfTypeParser(GenericTypeParser):
|
||||
mapped_type = Union
|
||||
|
||||
@@ -14,18 +18,22 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
self, name, properties, **kwargs: Unpack[TypeParserOptions]
|
||||
):
|
||||
if "oneOf" not in properties:
|
||||
raise ValueError(f"Invalid JSON Schema: {properties}")
|
||||
raise InvalidSchemaException(
|
||||
f"Invalid JSON Schema: {properties}", invalid_field="oneOf"
|
||||
)
|
||||
|
||||
if not isinstance(properties["oneOf"], list) or len(properties["oneOf"]) == 0:
|
||||
raise ValueError(f"Invalid JSON Schema: {properties['oneOf']}")
|
||||
raise InvalidSchemaException(
|
||||
f"Invalid JSON Schema: {properties['oneOf']}", invalid_field="oneOf"
|
||||
)
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
|
||||
sub_properties = properties["oneOf"]
|
||||
|
||||
sub_types = [
|
||||
GenericTypeParser.type_from_properties(name, subProperty, **kwargs)
|
||||
for subProperty in sub_properties
|
||||
GenericTypeParser.type_from_properties(
|
||||
f"{name}_sub{i}", subProperty, **kwargs
|
||||
)
|
||||
for i, subProperty in enumerate(properties["oneOf"])
|
||||
]
|
||||
|
||||
if not kwargs.get("required", False):
|
||||
@@ -37,8 +45,7 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
# they were added by OpenAPI and not all implementations may support them,
|
||||
# and they do not always generate a model one-to-one to the Pydantic model
|
||||
# TL;DR: Discriminators were added by OpenAPI and not a Official JSON Schema feature
|
||||
discriminator = properties.get("discriminator")
|
||||
if discriminator is not None:
|
||||
if (discriminator := properties.get("discriminator")) is not None:
|
||||
validated_type = self._build_type_one_of_with_discriminator(
|
||||
subfield_types, discriminator
|
||||
)
|
||||
@@ -49,13 +56,15 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
|
||||
@staticmethod
|
||||
def _build_type_one_of_with_discriminator(
|
||||
subfield_types: list[Annotated], discriminator_prop: dict
|
||||
) -> Annotated:
|
||||
subfield_types: list[Annotation], discriminator_prop: dict
|
||||
) -> Annotation:
|
||||
"""
|
||||
Build a type with a discriminator.
|
||||
"""
|
||||
if not isinstance(discriminator_prop, dict):
|
||||
raise ValueError("Discriminator must be a dictionary")
|
||||
raise InvalidSchemaException(
|
||||
"Discriminator must be a dictionary", invalid_field="discriminator"
|
||||
)
|
||||
|
||||
for field in subfield_types:
|
||||
field_type, field_info = get_args(field)
|
||||
@@ -63,18 +72,22 @@ class OneOfTypeParser(GenericTypeParser):
|
||||
if issubclass(field_type, BaseModel):
|
||||
continue
|
||||
|
||||
raise ValueError(
|
||||
"When using a discriminator, all subfield types must be of type 'object'."
|
||||
raise InvalidSchemaException(
|
||||
"When using a discriminator, all subfield types must be of type 'object'.",
|
||||
invalid_field="discriminator",
|
||||
)
|
||||
|
||||
property_name = discriminator_prop.get("propertyName")
|
||||
if property_name is None or not isinstance(property_name, str):
|
||||
raise ValueError("Discriminator must have a 'propertyName' key")
|
||||
raise InvalidSchemaException(
|
||||
"Discriminator must have a 'propertyName' key",
|
||||
invalid_field="propertyName",
|
||||
)
|
||||
|
||||
return Annotated[Union[(*subfield_types,)], Field(discriminator=property_name)]
|
||||
|
||||
@staticmethod
|
||||
def _build_type_one_of_with_func(subfield_types: list[Annotated]) -> Annotated:
|
||||
def _build_type_one_of_with_func(subfield_types: list[Annotation]) -> Annotation:
|
||||
"""
|
||||
Build a type with a validation function for the oneOf constraint.
|
||||
"""
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from jambo.exceptions import InternalAssertionException, InvalidSchemaException
|
||||
from jambo.parser import GenericTypeParser
|
||||
from jambo.types import RefCacheDict
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
from typing_extensions import Any, ForwardRef, Literal, TypeVar, Union, Unpack
|
||||
from typing_extensions import ForwardRef, Literal, Union, Unpack
|
||||
|
||||
|
||||
RefType = TypeVar("RefType", bound=Union[type, ForwardRef])
|
||||
RefType = Union[type, ForwardRef]
|
||||
|
||||
RefStrategy = Literal["forward_ref", "def_ref"]
|
||||
|
||||
@@ -13,21 +16,22 @@ class RefTypeParser(GenericTypeParser):
|
||||
json_schema_type = "$ref"
|
||||
|
||||
def from_properties_impl(
|
||||
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[RefType, dict]:
|
||||
if "$ref" not in properties:
|
||||
raise ValueError(f"RefTypeParser: Missing $ref in properties for {name}")
|
||||
raise InvalidSchemaException(
|
||||
f"Missing $ref in properties for {name}", invalid_field="$ref"
|
||||
)
|
||||
|
||||
context = kwargs.get("context")
|
||||
if context is None:
|
||||
raise RuntimeError(
|
||||
f"RefTypeParser: Missing `content` in properties for {name}"
|
||||
if kwargs.get("context") is None:
|
||||
raise InternalAssertionException(
|
||||
"`context` must be provided in kwargs for RefTypeParser"
|
||||
)
|
||||
|
||||
ref_cache = kwargs.get("ref_cache")
|
||||
if ref_cache is None:
|
||||
raise RuntimeError(
|
||||
f"RefTypeParser: Missing `ref_cache` in properties for {name}"
|
||||
raise InternalAssertionException(
|
||||
"`ref_cache` must be provided in kwargs for RefTypeParser"
|
||||
)
|
||||
|
||||
mapped_properties = self.mappings_properties_builder(properties, **kwargs)
|
||||
@@ -41,19 +45,19 @@ class RefTypeParser(GenericTypeParser):
|
||||
# If the reference is either processing or already cached
|
||||
return ref_state, mapped_properties
|
||||
|
||||
ref_cache[ref_name] = self._parse_from_strategy(
|
||||
ref_strategy, ref_name, ref_property, **kwargs
|
||||
)
|
||||
ref = self._parse_from_strategy(ref_strategy, ref_name, ref_property, **kwargs)
|
||||
ref_cache[ref_name] = ref
|
||||
|
||||
return ref_cache[ref_name], mapped_properties
|
||||
return ref, mapped_properties
|
||||
|
||||
def _parse_from_strategy(
|
||||
self,
|
||||
ref_strategy: RefStrategy,
|
||||
ref_name: str,
|
||||
ref_property: dict[str, Any],
|
||||
ref_property: JSONSchema,
|
||||
**kwargs: Unpack[TypeParserOptions],
|
||||
):
|
||||
) -> RefType:
|
||||
mapped_type: RefType
|
||||
match ref_strategy:
|
||||
case "forward_ref":
|
||||
mapped_type = ForwardRef(ref_name)
|
||||
@@ -62,14 +66,14 @@ class RefTypeParser(GenericTypeParser):
|
||||
ref_name, ref_property, **kwargs
|
||||
)
|
||||
case _:
|
||||
raise ValueError(
|
||||
f"RefTypeParser: Unsupported $ref {ref_property['$ref']}"
|
||||
raise InvalidSchemaException(
|
||||
f"Unsupported $ref {ref_property['$ref']}", invalid_field="$ref"
|
||||
)
|
||||
|
||||
return mapped_type
|
||||
|
||||
def _get_ref_from_cache(
|
||||
self, ref_name: str, ref_cache: dict[str, type]
|
||||
self, ref_name: str, ref_cache: RefCacheDict
|
||||
) -> RefType | type | None:
|
||||
try:
|
||||
ref_state = ref_cache[ref_name]
|
||||
@@ -84,42 +88,48 @@ class RefTypeParser(GenericTypeParser):
|
||||
# If the reference is not in the cache, we will set it to None
|
||||
ref_cache[ref_name] = None
|
||||
|
||||
return None
|
||||
|
||||
def _examine_ref_strategy(
|
||||
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[RefStrategy, str, dict] | None:
|
||||
if properties["$ref"] == "#":
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[RefStrategy, str, JSONSchema]:
|
||||
if properties.get("$ref") == "#":
|
||||
ref_name = kwargs["context"].get("title")
|
||||
if ref_name is None:
|
||||
raise ValueError(
|
||||
"RefTypeParser: Missing title in properties for $ref of Root Reference"
|
||||
raise InvalidSchemaException(
|
||||
"Missing title in properties for $ref of Root Reference",
|
||||
invalid_field="title",
|
||||
)
|
||||
return "forward_ref", ref_name, {}
|
||||
|
||||
if properties["$ref"].startswith("#/$defs/"):
|
||||
if properties.get("$ref", "").startswith("#/$defs/"):
|
||||
target_name, target_property = self._extract_target_ref(
|
||||
name, properties, **kwargs
|
||||
)
|
||||
return "def_ref", target_name, target_property
|
||||
|
||||
raise ValueError(
|
||||
"RefTypeParser: Only Root and $defs references are supported at the moment"
|
||||
raise InvalidSchemaException(
|
||||
"Only Root and $defs references are supported at the moment",
|
||||
invalid_field="$ref",
|
||||
)
|
||||
|
||||
def _extract_target_ref(
|
||||
self, name: str, properties: dict[str, Any], **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[str, dict]:
|
||||
self, name: str, properties: JSONSchema, **kwargs: Unpack[TypeParserOptions]
|
||||
) -> tuple[str, JSONSchema]:
|
||||
target_name = None
|
||||
target_property = kwargs["context"]
|
||||
for prop_name in properties["$ref"].split("/")[1:]:
|
||||
if prop_name not in target_property:
|
||||
raise ValueError(
|
||||
f"RefTypeParser: Missing {prop_name} in"
|
||||
" properties for $ref {properties['$ref']}"
|
||||
raise InvalidSchemaException(
|
||||
f"Missing {prop_name} in properties for $ref {properties['$ref']}",
|
||||
invalid_field=prop_name,
|
||||
)
|
||||
target_name = prop_name
|
||||
target_property = target_property[prop_name]
|
||||
target_property = target_property[prop_name] # type: ignore
|
||||
|
||||
if target_name is None or target_property is None:
|
||||
raise ValueError(f"RefTypeParser: Invalid $ref {properties['$ref']}")
|
||||
if not isinstance(target_name, str) or target_property is None:
|
||||
raise InvalidSchemaException(
|
||||
f"Invalid $ref {properties['$ref']}", invalid_field="$ref"
|
||||
)
|
||||
|
||||
return target_name, target_property
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
from jambo.types.type_parser_options import TypeParserOptions
|
||||
|
||||
from pydantic import AnyUrl, EmailStr
|
||||
from typing_extensions import Unpack
|
||||
from typing_extensions import Any, Unpack
|
||||
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
@@ -18,23 +19,22 @@ class StringTypeParser(GenericTypeParser):
|
||||
"maxLength": "max_length",
|
||||
"minLength": "min_length",
|
||||
"pattern": "pattern",
|
||||
"format": "format",
|
||||
}
|
||||
|
||||
format_type_mapping = {
|
||||
# 7.3.1. Dates, Times, and Duration
|
||||
# [7.3.1](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.1). Dates, Times, and Duration
|
||||
"date": date,
|
||||
"time": time,
|
||||
"date-time": datetime,
|
||||
"duration": timedelta,
|
||||
# 7.3.2. Email Addresses
|
||||
# [7.3.2](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.2). Email Addresses
|
||||
"email": EmailStr,
|
||||
# 7.3.3. Hostnames
|
||||
# [7.3.3](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.3). Hostnames
|
||||
"hostname": str,
|
||||
# 7.3.4. IP Addresses
|
||||
# [7.3.4](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.4). IP Addresses
|
||||
"ipv4": IPv4Address,
|
||||
"ipv6": IPv6Address,
|
||||
# 7.3.5. Resource Identifiers
|
||||
# [7.3.5](https://json-schema.org/draft/2020-12/draft-bhutton-json-schema-validation-00#rfc.section.7.3.5). Resource Identifiers
|
||||
"uri": AnyUrl,
|
||||
# "iri" # Not supported by pydantic and currently not supported by jambo
|
||||
"uuid": UUID,
|
||||
@@ -54,10 +54,45 @@ class StringTypeParser(GenericTypeParser):
|
||||
return str, mapped_properties
|
||||
|
||||
if format_type not in self.format_type_mapping:
|
||||
raise ValueError(f"Unsupported string format: {format_type}")
|
||||
raise InvalidSchemaException(
|
||||
f"Unsupported string format: {format_type}", invalid_field="format"
|
||||
)
|
||||
|
||||
mapped_type = self.format_type_mapping[format_type]
|
||||
if format_type in self.format_pattern_mapping:
|
||||
mapped_properties["pattern"] = self.format_pattern_mapping[format_type]
|
||||
|
||||
if "examples" in mapped_properties:
|
||||
mapped_properties["examples"] = [
|
||||
self._parse_example(example, format_type, mapped_type)
|
||||
for example in mapped_properties["examples"]
|
||||
]
|
||||
|
||||
if "json_schema_extra" not in mapped_properties:
|
||||
mapped_properties["json_schema_extra"] = {}
|
||||
mapped_properties["json_schema_extra"]["format"] = format_type
|
||||
|
||||
return mapped_type, mapped_properties
|
||||
|
||||
def _parse_example(
|
||||
self, example: Any, format_type: str, mapped_type: type[Any]
|
||||
) -> Any:
|
||||
"""
|
||||
Parse example from JSON Schema format to python format
|
||||
:param example: Example Value
|
||||
:param format_type: Format Type
|
||||
:param mapped_type: Type to parse
|
||||
:return: Example parsed
|
||||
"""
|
||||
match format_type:
|
||||
case "date" | "time" | "date-time":
|
||||
return mapped_type.fromisoformat(example)
|
||||
case "duration":
|
||||
# TODO: Implement duration parser
|
||||
raise NotImplementedError
|
||||
case "ipv4" | "ipv6":
|
||||
return mapped_type(example)
|
||||
case "uuid":
|
||||
return mapped_type(example)
|
||||
case _:
|
||||
return example
|
||||
|
||||
0
jambo/py.typed
Normal file
0
jambo/py.typed
Normal file
@@ -1,9 +1,11 @@
|
||||
from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException
|
||||
from jambo.parser import ObjectTypeParser, RefTypeParser
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
from jambo.types import JSONSchema, RefCacheDict
|
||||
|
||||
from jsonschema.exceptions import SchemaError
|
||||
from jsonschema.validators import validator_for
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import Optional
|
||||
|
||||
|
||||
class SchemaConverter:
|
||||
@@ -15,22 +17,67 @@ class SchemaConverter:
|
||||
fields and types. The generated model can be used for data validation and serialization.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def build(schema: JSONSchema) -> type[BaseModel]:
|
||||
def __init__(
|
||||
self, namespace_registry: Optional[dict[str, RefCacheDict]] = None
|
||||
) -> None:
|
||||
if namespace_registry is None:
|
||||
namespace_registry = dict()
|
||||
self._namespace_registry = namespace_registry
|
||||
|
||||
def build_with_cache(
|
||||
self,
|
||||
schema: JSONSchema,
|
||||
ref_cache: Optional[RefCacheDict] = None,
|
||||
without_cache: bool = False,
|
||||
) -> type[BaseModel]:
|
||||
"""
|
||||
Converts a JSON Schema to a Pydantic model.
|
||||
:param schema: The JSON Schema to convert.
|
||||
:return: A Pydantic model class.
|
||||
This is the instance method version of `build` and uses the instance's reference cache if none is provided.
|
||||
Use this method if you want to utilize the instance's reference cache.
|
||||
|
||||
:param schema: The JSON Schema to convert.
|
||||
:param ref_cache: An optional reference cache to use during conversion.
|
||||
:param without_cache: Whether to use a clean reference cache for this conversion.
|
||||
:return: The generated Pydantic model.
|
||||
"""
|
||||
local_ref_cache: RefCacheDict
|
||||
|
||||
if without_cache:
|
||||
local_ref_cache = dict()
|
||||
elif ref_cache is None:
|
||||
namespace = schema.get("$id", "default")
|
||||
local_ref_cache = self._namespace_registry.setdefault(namespace, dict())
|
||||
else:
|
||||
local_ref_cache = ref_cache
|
||||
|
||||
return self.build(schema, local_ref_cache)
|
||||
|
||||
@staticmethod
|
||||
def build(
|
||||
schema: JSONSchema, ref_cache: Optional[RefCacheDict] = None
|
||||
) -> type[BaseModel]:
|
||||
"""
|
||||
Converts a JSON Schema to a Pydantic model.
|
||||
This method doesn't use a reference cache if none is provided.
|
||||
:param schema: The JSON Schema to convert.
|
||||
:param ref_cache: An optional reference cache to use during conversion, if provided `with_clean_cache` will be ignored.
|
||||
:return: The generated Pydantic model.
|
||||
"""
|
||||
if ref_cache is None:
|
||||
ref_cache = dict()
|
||||
|
||||
try:
|
||||
validator = validator_for(schema)
|
||||
validator.check_schema(schema)
|
||||
except SchemaError as e:
|
||||
raise ValueError(f"Invalid JSON Schema: {e}")
|
||||
validator.check_schema(schema) # type: ignore
|
||||
except SchemaError as err:
|
||||
raise InvalidSchemaException(
|
||||
"Validation of JSON Schema failed.", cause=err
|
||||
) from err
|
||||
|
||||
if "title" not in schema:
|
||||
raise ValueError("JSON Schema must have a title.")
|
||||
raise InvalidSchemaException(
|
||||
"Schema must have a title.", invalid_field="title"
|
||||
)
|
||||
|
||||
schema_type = SchemaConverter._get_schema_type(schema)
|
||||
|
||||
@@ -38,10 +85,11 @@ class SchemaConverter:
|
||||
case "object":
|
||||
return ObjectTypeParser.to_model(
|
||||
schema["title"],
|
||||
schema["properties"],
|
||||
schema.get("properties", {}),
|
||||
schema.get("required", []),
|
||||
context=schema,
|
||||
ref_cache=dict(),
|
||||
ref_cache=ref_cache,
|
||||
required=True,
|
||||
)
|
||||
|
||||
case "$ref":
|
||||
@@ -49,14 +97,49 @@ class SchemaConverter:
|
||||
schema["title"],
|
||||
schema,
|
||||
context=schema,
|
||||
ref_cache=dict(),
|
||||
ref_cache=ref_cache,
|
||||
required=True,
|
||||
)
|
||||
return parsed_model
|
||||
case _:
|
||||
raise TypeError(f"Unsupported schema type: {schema_type}")
|
||||
unsupported_type = (
|
||||
f"type:{schema_type}" if schema_type else "missing type"
|
||||
)
|
||||
raise UnsupportedSchemaException(
|
||||
"Only object and $ref schema types are supported.",
|
||||
unsupported_field=unsupported_type,
|
||||
)
|
||||
|
||||
def clear_ref_cache(self, namespace: Optional[str] = "default") -> None:
|
||||
"""
|
||||
Clears the reference cache.
|
||||
"""
|
||||
if namespace is None:
|
||||
self._namespace_registry.clear()
|
||||
return
|
||||
|
||||
if namespace in self._namespace_registry:
|
||||
self._namespace_registry[namespace].clear()
|
||||
|
||||
def get_cached_ref(
|
||||
self, ref_name: str, namespace: str = "default"
|
||||
) -> Optional[type]:
|
||||
"""
|
||||
Gets a cached reference from the reference cache.
|
||||
:param ref_name: The name of the reference to get.
|
||||
:return: The cached reference, or None if not found.
|
||||
"""
|
||||
cached_type = self._namespace_registry.get(
|
||||
namespace, {}
|
||||
).get(ref_name)
|
||||
|
||||
if isinstance(cached_type, type):
|
||||
return cached_type
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_schema_type(schema: JSONSchema) -> str:
|
||||
def _get_schema_type(schema: JSONSchema) -> str | None:
|
||||
"""
|
||||
Returns the type of the schema.
|
||||
:param schema: The JSON Schema to check.
|
||||
@@ -65,4 +148,11 @@ class SchemaConverter:
|
||||
if "$ref" in schema:
|
||||
return "$ref"
|
||||
|
||||
return schema.get("type", "undefined")
|
||||
type_value = schema.get("type")
|
||||
if isinstance(type_value, list):
|
||||
raise InvalidSchemaException(
|
||||
"Invalid schema: 'type' cannot be a list at the top level",
|
||||
invalid_field=str(schema),
|
||||
)
|
||||
|
||||
return type_value
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
from .json_schema_type import (
|
||||
JSONSchema,
|
||||
JSONSchemaNativeTypes,
|
||||
JSONSchemaType,
|
||||
JSONType,
|
||||
)
|
||||
from .type_parser_options import RefCacheDict, TypeParserOptions
|
||||
|
||||
|
||||
__all__ = [
|
||||
"JSONSchemaType",
|
||||
"JSONSchemaNativeTypes",
|
||||
"JSONType",
|
||||
"JSONSchema",
|
||||
"RefCacheDict",
|
||||
"TypeParserOptions",
|
||||
]
|
||||
|
||||
@@ -1,93 +1,80 @@
|
||||
from typing_extensions import Dict, List, Literal, TypedDict, Union
|
||||
from __future__ import annotations
|
||||
|
||||
from typing_extensions import (
|
||||
Dict,
|
||||
List,
|
||||
Literal,
|
||||
TypedDict,
|
||||
Union,
|
||||
)
|
||||
|
||||
from types import NoneType
|
||||
|
||||
|
||||
# Primitive JSON types
|
||||
JSONSchemaType = Literal[
|
||||
"string", "number", "integer", "boolean", "object", "array", "null"
|
||||
]
|
||||
|
||||
|
||||
JSONSchemaNativeTypes: tuple[type, ...] = (
|
||||
str,
|
||||
int,
|
||||
str,
|
||||
float,
|
||||
int,
|
||||
bool,
|
||||
list,
|
||||
set,
|
||||
NoneType,
|
||||
)
|
||||
|
||||
|
||||
JSONType = Union[str, int, float, bool, None, Dict[str, "JSONType"], List["JSONType"]]
|
||||
|
||||
|
||||
class JSONSchema(TypedDict, total=False):
|
||||
# Basic metadata
|
||||
title: str
|
||||
description: str
|
||||
default: JSONType
|
||||
examples: List[JSONType]
|
||||
|
||||
# Type definitions
|
||||
type: Union[JSONSchemaType, List[JSONSchemaType]]
|
||||
|
||||
# Object-specific keywords
|
||||
properties: Dict[str, "JSONSchema"]
|
||||
required: List[str]
|
||||
additionalProperties: Union[bool, "JSONSchema"]
|
||||
minProperties: int
|
||||
maxProperties: int
|
||||
patternProperties: Dict[str, "JSONSchema"]
|
||||
dependencies: Dict[str, Union[List[str], "JSONSchema"]]
|
||||
|
||||
# Array-specific keywords
|
||||
items: Union["JSONSchema", List["JSONSchema"]]
|
||||
additionalItems: Union[bool, "JSONSchema"]
|
||||
minItems: int
|
||||
maxItems: int
|
||||
uniqueItems: bool
|
||||
|
||||
# String-specific keywords
|
||||
minLength: int
|
||||
maxLength: int
|
||||
pattern: str
|
||||
format: str
|
||||
|
||||
# Number-specific keywords
|
||||
minimum: float
|
||||
maximum: float
|
||||
exclusiveMinimum: float
|
||||
exclusiveMaximum: float
|
||||
multipleOf: float
|
||||
|
||||
# Enum and const
|
||||
enum: List[JSONType]
|
||||
const: JSONType
|
||||
|
||||
# Conditionals
|
||||
if_: "JSONSchema" # 'if' is a reserved word in Python
|
||||
then: "JSONSchema"
|
||||
else_: "JSONSchema" # 'else' is also a reserved word
|
||||
|
||||
# Combination keywords
|
||||
allOf: List["JSONSchema"]
|
||||
anyOf: List["JSONSchema"]
|
||||
oneOf: List["JSONSchema"]
|
||||
not_: "JSONSchema" # 'not' is a reserved word
|
||||
|
||||
|
||||
# Fix forward references
|
||||
JSONSchema.__annotations__["properties"] = Dict[str, JSONSchema]
|
||||
JSONSchema.__annotations__["items"] = Union[JSONSchema, List[JSONSchema]]
|
||||
JSONSchema.__annotations__["additionalItems"] = Union[bool, JSONSchema]
|
||||
JSONSchema.__annotations__["additionalProperties"] = Union[bool, JSONSchema]
|
||||
JSONSchema.__annotations__["patternProperties"] = Dict[str, JSONSchema]
|
||||
JSONSchema.__annotations__["dependencies"] = Dict[str, Union[List[str], JSONSchema]]
|
||||
JSONSchema.__annotations__["if_"] = JSONSchema
|
||||
JSONSchema.__annotations__["then"] = JSONSchema
|
||||
JSONSchema.__annotations__["else_"] = JSONSchema
|
||||
JSONSchema.__annotations__["allOf"] = List[JSONSchema]
|
||||
JSONSchema.__annotations__["anyOf"] = List[JSONSchema]
|
||||
JSONSchema.__annotations__["oneOf"] = List[JSONSchema]
|
||||
JSONSchema.__annotations__["not_"] = JSONSchema
|
||||
# Dynamically define TypedDict with JSON Schema keywords
|
||||
JSONSchema = TypedDict(
|
||||
"JSONSchema",
|
||||
{
|
||||
"$id": str,
|
||||
"$schema": str,
|
||||
"$ref": str,
|
||||
"$anchor": str,
|
||||
"$comment": str,
|
||||
"$defs": Dict[str, "JSONSchema"],
|
||||
"title": str,
|
||||
"description": str,
|
||||
"default": JSONType,
|
||||
"examples": List[JSONType],
|
||||
"type": JSONSchemaType | List[JSONSchemaType],
|
||||
"enum": List[JSONType],
|
||||
"const": JSONType,
|
||||
"properties": Dict[str, "JSONSchema"],
|
||||
"patternProperties": Dict[str, "JSONSchema"],
|
||||
"additionalProperties": Union[bool, "JSONSchema"],
|
||||
"required": List[str],
|
||||
"minProperties": int,
|
||||
"maxProperties": int,
|
||||
"dependencies": Dict[str, Union[List[str], "JSONSchema"]],
|
||||
"items": "JSONSchema",
|
||||
"prefixItems": List["JSONSchema"],
|
||||
"additionalItems": Union[bool, "JSONSchema"],
|
||||
"contains": "JSONSchema",
|
||||
"minItems": int,
|
||||
"maxItems": int,
|
||||
"uniqueItems": bool,
|
||||
"minLength": int,
|
||||
"maxLength": int,
|
||||
"pattern": str,
|
||||
"format": str,
|
||||
"minimum": float,
|
||||
"maximum": float,
|
||||
"exclusiveMinimum": Union[bool, float],
|
||||
"exclusiveMaximum": Union[bool, float],
|
||||
"multipleOf": float,
|
||||
"if": "JSONSchema",
|
||||
"then": "JSONSchema",
|
||||
"else": "JSONSchema",
|
||||
"allOf": List["JSONSchema"],
|
||||
"anyOf": List["JSONSchema"],
|
||||
"oneOf": List["JSONSchema"],
|
||||
"not": "JSONSchema",
|
||||
},
|
||||
total=False, # all fields optional
|
||||
)
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from jambo.types.json_schema_type import JSONSchema
|
||||
|
||||
from typing_extensions import TypedDict
|
||||
from typing_extensions import ForwardRef, MutableMapping, TypedDict
|
||||
|
||||
|
||||
RefCacheDict = MutableMapping[str, ForwardRef | type | None]
|
||||
|
||||
|
||||
class TypeParserOptions(TypedDict):
|
||||
required: bool
|
||||
context: JSONSchema
|
||||
ref_cache: dict[str, type]
|
||||
ref_cache: RefCacheDict
|
||||
|
||||
@@ -18,25 +18,28 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
]
|
||||
license = { file = "LICENSE" }
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
|
||||
# Project Dependencies
|
||||
dependencies = [
|
||||
"email-validator>=2.2.0",
|
||||
"jsonschema>=4.23.0",
|
||||
"pydantic>=2.10.6",
|
||||
"pydantic>=2.12.4",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"coverage>=7.8.0",
|
||||
"mypy>=1.18.1",
|
||||
"poethepoet>=0.33.1",
|
||||
"pre-commit>=4.2.0",
|
||||
"ruff>=0.11.4",
|
||||
"sphinx>=8.1.3",
|
||||
"sphinx-autobuild>=2024.10.3",
|
||||
"sphinx-autodoc-typehints>=3.0.1",
|
||||
"sphinx-rtd-theme>=3.0.2",
|
||||
"types-jsonschema>=4.25.1.20250822",
|
||||
]
|
||||
|
||||
|
||||
@@ -50,7 +53,8 @@ repository = "https://github.com/HideyoshiNakazone/jambo.git"
|
||||
create-hooks = "bash .githooks/set-hooks.sh"
|
||||
tests = "python -m coverage run -m unittest discover -v"
|
||||
tests-report = "python -m coverage xml"
|
||||
serve-docs = "sphinx-autobuild docs/source docs/build"
|
||||
type-check = "mypy jambo"
|
||||
serve-docs = "sphinx-autobuild docs/source docs/build"
|
||||
|
||||
# Build System
|
||||
[tool.hatch.version]
|
||||
@@ -83,3 +87,8 @@ section-order=[
|
||||
"standard-library",
|
||||
]
|
||||
lines-after-imports = 2
|
||||
|
||||
|
||||
[tool.pyright]
|
||||
venvPath = "."
|
||||
venv = ".venv"
|
||||
|
||||
0
tests/exceptions/__init__.py
Normal file
0
tests/exceptions/__init__.py
Normal file
21
tests/exceptions/test_internal_assertion_exception.py
Normal file
21
tests/exceptions/test_internal_assertion_exception.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from jambo.exceptions.internal_assertion_exception import InternalAssertionException
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestInternalAssertionException(TestCase):
|
||||
def test_inheritance(self):
|
||||
self.assertTrue(issubclass(InternalAssertionException, RuntimeError))
|
||||
|
||||
def test_message(self):
|
||||
message = "This is an internal assertion error."
|
||||
|
||||
expected_message = (
|
||||
f"Internal Assertion Failed: {message}\n"
|
||||
"This is likely a bug in Jambo. Please report it at"
|
||||
)
|
||||
|
||||
with self.assertRaises(InternalAssertionException) as ctx:
|
||||
raise InternalAssertionException(message)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
44
tests/exceptions/test_invalid_schema_exception.py
Normal file
44
tests/exceptions/test_invalid_schema_exception.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from jambo.exceptions.invalid_schema_exception import InvalidSchemaException
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestInternalAssertionException(TestCase):
|
||||
def test_inheritance(self):
|
||||
self.assertTrue(issubclass(InvalidSchemaException, ValueError))
|
||||
|
||||
def test_message(self):
|
||||
message = "This is an internal assertion error."
|
||||
|
||||
expected_message = f"Invalid JSON Schema: {message}"
|
||||
|
||||
with self.assertRaises(InvalidSchemaException) as ctx:
|
||||
raise InvalidSchemaException(message)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
|
||||
def test_invalid_field(self):
|
||||
message = "This is an internal assertion error."
|
||||
invalid_field = "testField"
|
||||
|
||||
expected_message = (
|
||||
f"Invalid JSON Schema: {message} (invalid field: {invalid_field})"
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException) as ctx:
|
||||
raise InvalidSchemaException(message, invalid_field=invalid_field)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
|
||||
def test_cause(self):
|
||||
message = "This is an internal assertion error."
|
||||
cause = ValueError("Underlying cause")
|
||||
|
||||
expected_message = (
|
||||
f"Invalid JSON Schema: {message} (caused by ValueError: Underlying cause)"
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException) as ctx:
|
||||
raise InvalidSchemaException(message, cause=cause)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
31
tests/exceptions/test_unsupported_schema_exception.py
Normal file
31
tests/exceptions/test_unsupported_schema_exception.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from jambo.exceptions.unsupported_schema_exception import UnsupportedSchemaException
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestUnsupportedSchemaException(TestCase):
|
||||
def test_inheritance(self):
|
||||
self.assertTrue(issubclass(UnsupportedSchemaException, ValueError))
|
||||
|
||||
def test_message(self):
|
||||
message = "This is an internal assertion error."
|
||||
|
||||
expected_message = f"Unsupported JSON Schema: {message}"
|
||||
|
||||
with self.assertRaises(UnsupportedSchemaException) as ctx:
|
||||
raise UnsupportedSchemaException(message)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
|
||||
def test_unsupported_field(self):
|
||||
message = "This is an internal assertion error."
|
||||
invalid_field = "testField"
|
||||
|
||||
expected_message = (
|
||||
f"Unsupported JSON Schema: {message} (unsupported field: {invalid_field})"
|
||||
)
|
||||
|
||||
with self.assertRaises(UnsupportedSchemaException) as ctx:
|
||||
raise UnsupportedSchemaException(message, unsupported_field=invalid_field)
|
||||
|
||||
self.assertEqual(str(ctx.exception), expected_message)
|
||||
@@ -1,5 +1,8 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser.allof_type_parser import AllOfTypeParser
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
@@ -39,16 +42,16 @@ class TestAllOfTypeParser(TestCase):
|
||||
}
|
||||
|
||||
type_parsing, type_validator = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="John", age=101)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="", age=30)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="John Invalid", age=30)
|
||||
|
||||
obj = type_parsing(name="John", age=30)
|
||||
@@ -84,13 +87,13 @@ class TestAllOfTypeParser(TestCase):
|
||||
}
|
||||
|
||||
type_parsing, type_validator = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(name="John")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
type_parsing(age=30)
|
||||
|
||||
obj = type_parsing(name="John", age=30)
|
||||
@@ -113,7 +116,7 @@ class TestAllOfTypeParser(TestCase):
|
||||
}
|
||||
|
||||
type_parsing, type_validator = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
self.assertEqual(type_parsing, str)
|
||||
@@ -134,7 +137,7 @@ class TestAllOfTypeParser(TestCase):
|
||||
}
|
||||
|
||||
type_parsing, type_validator = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
self.assertEqual(type_parsing, str)
|
||||
@@ -154,8 +157,8 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties, ref_cache={})
|
||||
|
||||
def test_all_of_invalid_type_not_present(self):
|
||||
properties = {
|
||||
@@ -167,8 +170,8 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties, ref_cache={})
|
||||
|
||||
def test_all_of_invalid_type_in_fields(self):
|
||||
properties = {
|
||||
@@ -180,8 +183,8 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties, ref_cache={})
|
||||
|
||||
def test_all_of_invalid_type_not_all_equal(self):
|
||||
"""
|
||||
@@ -196,8 +199,8 @@ class TestAllOfTypeParser(TestCase):
|
||||
]
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties, ref_cache={})
|
||||
|
||||
def test_all_of_description_field(self):
|
||||
"""
|
||||
@@ -234,7 +237,9 @@ class TestAllOfTypeParser(TestCase):
|
||||
],
|
||||
}
|
||||
|
||||
type_parsing, _ = AllOfTypeParser().from_properties("placeholder", properties)
|
||||
type_parsing, _ = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
type_parsing.model_json_schema()["properties"]["name"]["description"],
|
||||
@@ -272,7 +277,9 @@ class TestAllOfTypeParser(TestCase):
|
||||
],
|
||||
}
|
||||
|
||||
type_parsing, _ = AllOfTypeParser().from_properties("placeholder", properties)
|
||||
type_parsing, _ = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
obj = type_parsing()
|
||||
self.assertEqual(obj.name, "John")
|
||||
self.assertEqual(obj.age, 30)
|
||||
@@ -304,5 +311,52 @@ class TestAllOfTypeParser(TestCase):
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
AllOfTypeParser().from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AllOfTypeParser().from_properties("placeholder", properties, ref_cache={})
|
||||
|
||||
def test_all_of_with_root_examples(self):
|
||||
"""
|
||||
Tests the AllOfTypeParser with examples.
|
||||
"""
|
||||
|
||||
properties = {
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"maxLength": 4,
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
"examples": [
|
||||
{"name": "John"},
|
||||
{"name": "Jane"},
|
||||
{"name": "Doe"},
|
||||
{"name": "Jack"},
|
||||
],
|
||||
}
|
||||
|
||||
type_parsed, type_properties = AllOfTypeParser().from_properties(
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
type_properties["examples"],
|
||||
[
|
||||
type_parsed(name="John"),
|
||||
type_parsed(name="Jane"),
|
||||
type_parsed(name="Doe"),
|
||||
type_parsed(name="Jack"),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser.anyof_type_parser import AnyOfTypeParser
|
||||
|
||||
from typing_extensions import Annotated, Union, get_args, get_origin
|
||||
@@ -14,7 +15,7 @@ class TestAnyOfTypeParser(TestCase):
|
||||
],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_any_of_with_invalid_properties(self):
|
||||
@@ -22,7 +23,7 @@ class TestAnyOfTypeParser(TestCase):
|
||||
"anyOf": None,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_any_of_string_or_int(self):
|
||||
@@ -95,5 +96,48 @@ class TestAnyOfTypeParser(TestCase):
|
||||
"default": 3.14,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
def test_anyof_with_examples(self):
|
||||
"""
|
||||
Tests the AnyOfTypeParser with a string or int type and examples.
|
||||
"""
|
||||
|
||||
properties = {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"examples": ["example string"],
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"examples": [123],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
parsed_type, _ = AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
type_1, type_2 = get_args(parsed_type)
|
||||
|
||||
self.assertEqual(get_args(type_1)[1].examples, ["example string"])
|
||||
|
||||
self.assertEqual(get_args(type_2)[1].examples, [123])
|
||||
|
||||
def test_any_of_with_root_examples(self):
|
||||
"""
|
||||
Tests the AnyOfTypeParser with a string or int type and examples.
|
||||
"""
|
||||
|
||||
properties = {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "integer"},
|
||||
],
|
||||
"examples": ["100", 100],
|
||||
}
|
||||
|
||||
_, type_validator = AnyOfTypeParser().from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_validator["examples"], ["100", 100])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import ArrayTypeParser
|
||||
|
||||
from typing_extensions import get_args
|
||||
@@ -18,6 +19,17 @@ class TestArrayTypeParser(TestCase):
|
||||
self.assertEqual(type_parsing.__origin__, list)
|
||||
self.assertEqual(element_type, str)
|
||||
|
||||
def test_array_parser_with_no_items(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
properties = {
|
||||
"default": ["a", "b", "c", "d"],
|
||||
"maxItems": 3,
|
||||
}
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_options_unique(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
@@ -67,7 +79,7 @@ class TestArrayTypeParser(TestCase):
|
||||
|
||||
properties = {"items": {"type": "string"}, "default": ["a", 1, "c"]}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_invalid_default_type(self):
|
||||
@@ -75,15 +87,15 @@ class TestArrayTypeParser(TestCase):
|
||||
|
||||
properties = {"items": {"type": "string"}, "default": 000}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
parser.from_properties("placeholder", properties)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties=properties)
|
||||
|
||||
def test_array_parser_with_invalid_default_min(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
properties = {"items": {"type": "string"}, "default": ["a"], "minItems": 2}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_invalid_default_max(self):
|
||||
@@ -95,5 +107,21 @@ class TestArrayTypeParser(TestCase):
|
||||
"maxItems": 3,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_array_parser_with_examples(self):
|
||||
parser = ArrayTypeParser()
|
||||
|
||||
properties = {
|
||||
"items": {"type": "integer"},
|
||||
"examples": [
|
||||
[1, 2, 3],
|
||||
[4, 5, 6],
|
||||
],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing.__origin__, list)
|
||||
self.assertEqual(type_validator["examples"], [[1, 2, 3], [4, 5, 6]])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import BooleanTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -39,5 +40,21 @@ class TestBoolTypeParser(TestCase):
|
||||
"default": "invalid",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties_impl("placeholder", properties)
|
||||
|
||||
def test_bool_parser_with_examples(self):
|
||||
parser = BooleanTypeParser()
|
||||
|
||||
properties = {
|
||||
"type": "boolean",
|
||||
"examples": [True, False],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties_impl(
|
||||
"placeholder", properties
|
||||
)
|
||||
|
||||
self.assertEqual(type_parsing, bool)
|
||||
self.assertEqual(type_validator["default"], None)
|
||||
self.assertEqual(type_validator["examples"], [True, False])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import ConstTypeParser
|
||||
|
||||
from typing_extensions import Annotated, Literal, get_args, get_origin
|
||||
@@ -11,7 +12,7 @@ class TestConstTypeParser(TestCase):
|
||||
parser = ConstTypeParser()
|
||||
|
||||
expected_const_value = "United States of America"
|
||||
properties = {"const": expected_const_value}
|
||||
properties = {"const": expected_const_value, "examples": [expected_const_value]}
|
||||
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"country", properties
|
||||
@@ -22,13 +23,14 @@ class TestConstTypeParser(TestCase):
|
||||
self.assertEqual(get_args(parsed_type), (expected_const_value,))
|
||||
|
||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
||||
self.assertEqual(parsed_properties["examples"], [expected_const_value])
|
||||
|
||||
def test_const_type_parser_non_hashable_value(self):
|
||||
"""Test const parser with non-hashable values (uses Annotated with validator)"""
|
||||
parser = ConstTypeParser()
|
||||
|
||||
expected_const_value = [1, 2, 3] # Lists are not hashable
|
||||
properties = {"const": expected_const_value}
|
||||
properties = {"const": expected_const_value, "examples": [expected_const_value]}
|
||||
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"list_const", properties
|
||||
@@ -39,13 +41,14 @@ class TestConstTypeParser(TestCase):
|
||||
self.assertIn(list, get_args(parsed_type))
|
||||
|
||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
||||
self.assertEqual(parsed_properties["examples"], [expected_const_value])
|
||||
|
||||
def test_const_type_parser_integer_value(self):
|
||||
"""Test const parser with integer values (uses Literal)"""
|
||||
parser = ConstTypeParser()
|
||||
|
||||
expected_const_value = 42
|
||||
properties = {"const": expected_const_value}
|
||||
properties = {"const": expected_const_value, "examples": [expected_const_value]}
|
||||
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"int_const", properties
|
||||
@@ -56,13 +59,14 @@ class TestConstTypeParser(TestCase):
|
||||
self.assertEqual(get_args(parsed_type), (expected_const_value,))
|
||||
|
||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
||||
self.assertEqual(parsed_properties["examples"], [expected_const_value])
|
||||
|
||||
def test_const_type_parser_boolean_value(self):
|
||||
"""Test const parser with boolean values (uses Literal)"""
|
||||
parser = ConstTypeParser()
|
||||
|
||||
expected_const_value = True
|
||||
properties = {"const": expected_const_value}
|
||||
properties = {"const": expected_const_value, "examples": [expected_const_value]}
|
||||
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"bool_const", properties
|
||||
@@ -73,6 +77,7 @@ class TestConstTypeParser(TestCase):
|
||||
self.assertEqual(get_args(parsed_type), (expected_const_value,))
|
||||
|
||||
self.assertEqual(parsed_properties["default"], expected_const_value)
|
||||
self.assertEqual(parsed_properties["examples"], [expected_const_value])
|
||||
|
||||
def test_const_type_parser_invalid_properties(self):
|
||||
parser = ConstTypeParser()
|
||||
@@ -80,7 +85,7 @@ class TestConstTypeParser(TestCase):
|
||||
expected_const_value = "United States of America"
|
||||
properties = {"notConst": expected_const_value}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
parser.from_properties_impl("invalid_country", properties)
|
||||
|
||||
self.assertIn(
|
||||
@@ -93,10 +98,10 @@ class TestConstTypeParser(TestCase):
|
||||
|
||||
properties = {"const": {}}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
parser.from_properties_impl("invalid_country", properties)
|
||||
|
||||
self.assertIn(
|
||||
"Const type invalid_country must have 'const' value of allowed types",
|
||||
str(context.exception),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import EnumTypeParser
|
||||
|
||||
from enum import Enum
|
||||
@@ -10,7 +11,7 @@ class TestEnumTypeParser(TestCase):
|
||||
|
||||
schema = {}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"TestEnum",
|
||||
schema,
|
||||
@@ -23,7 +24,7 @@ class TestEnumTypeParser(TestCase):
|
||||
"enum": "not_a_list",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"TestEnum",
|
||||
schema,
|
||||
@@ -81,10 +82,34 @@ class TestEnumTypeParser(TestCase):
|
||||
|
||||
def test_enum_type_parser_throws_invalid_enum_value(self):
|
||||
parser = EnumTypeParser()
|
||||
|
||||
|
||||
schema = {
|
||||
"enum": ["value1", 42, dict()],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties_impl("TestEnum", schema)
|
||||
|
||||
def test_enum_type_parser_creates_enum_with_examples(self):
|
||||
parser = EnumTypeParser()
|
||||
|
||||
schema = {
|
||||
"enum": ["value1", "value2", "value3"],
|
||||
"examples": ["value1", "value3"],
|
||||
}
|
||||
|
||||
parsed_type, parsed_properties = parser.from_properties_impl(
|
||||
"TestEnum",
|
||||
schema,
|
||||
)
|
||||
|
||||
self.assertIsInstance(parsed_type, type)
|
||||
self.assertTrue(issubclass(parsed_type, Enum))
|
||||
self.assertEqual(
|
||||
set(parsed_type.__members__.keys()), {"VALUE1", "VALUE2", "VALUE3"}
|
||||
)
|
||||
self.assertEqual(parsed_properties["default"], None)
|
||||
self.assertEqual(
|
||||
parsed_properties["examples"],
|
||||
[getattr(parsed_type, "VALUE1"), getattr(parsed_type, "VALUE3")],
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import FloatTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -22,6 +23,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"maximum": 10.5,
|
||||
"minimum": 1.0,
|
||||
"multipleOf": 0.5,
|
||||
"examples": [1.5, 2.5],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
@@ -30,6 +32,7 @@ class TestFloatTypeParser(TestCase):
|
||||
self.assertEqual(type_validator["le"], 10.5)
|
||||
self.assertEqual(type_validator["ge"], 1.0)
|
||||
self.assertEqual(type_validator["multiple_of"], 0.5)
|
||||
self.assertEqual(type_validator["examples"], [1.5, 2.5])
|
||||
|
||||
def test_float_parser_with_default(self):
|
||||
parser = FloatTypeParser()
|
||||
@@ -61,7 +64,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_maximum(self):
|
||||
@@ -75,7 +78,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_minimum(self):
|
||||
@@ -89,7 +92,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_exclusive_maximum(self):
|
||||
@@ -103,7 +106,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_exclusive_minimum(self):
|
||||
@@ -117,7 +120,7 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 0.5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_float_parser_with_default_invalid_multiple(self):
|
||||
@@ -131,5 +134,5 @@ class TestFloatTypeParser(TestCase):
|
||||
"multipleOf": 2.0,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import IntTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
@@ -22,6 +23,7 @@ class TestIntTypeParser(TestCase):
|
||||
"maximum": 10,
|
||||
"minimum": 1,
|
||||
"multipleOf": 2,
|
||||
"examples": [2, 4],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
@@ -30,6 +32,7 @@ class TestIntTypeParser(TestCase):
|
||||
self.assertEqual(type_validator["le"], 10)
|
||||
self.assertEqual(type_validator["ge"], 1)
|
||||
self.assertEqual(type_validator["multiple_of"], 2)
|
||||
self.assertEqual(type_validator["examples"], [2, 4])
|
||||
|
||||
def test_int_parser_with_default(self):
|
||||
parser = IntTypeParser()
|
||||
@@ -61,7 +64,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_maximum(self):
|
||||
@@ -75,7 +78,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_minimum(self):
|
||||
@@ -89,7 +92,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_exclusive_maximum(self):
|
||||
@@ -103,7 +106,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_exclusive_minimum(self):
|
||||
@@ -117,7 +120,7 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_int_parser_with_default_invalid_multipleOf(self):
|
||||
@@ -131,5 +134,5 @@ class TestIntTypeParser(TestCase):
|
||||
"multipleOf": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
@@ -16,6 +16,22 @@ class TestNullTypeParser(TestCase):
|
||||
self.assertEqual(type_parsing, type(None))
|
||||
self.assertEqual(type_validator, {"default": None})
|
||||
|
||||
def test_null_parser_with_examples(self):
|
||||
parser = NullTypeParser()
|
||||
|
||||
properties = {
|
||||
"type": "null",
|
||||
"examples": [None],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties_impl(
|
||||
"placeholder", properties
|
||||
)
|
||||
|
||||
self.assertEqual(type_parsing, type(None))
|
||||
self.assertEqual(type_validator["default"], None)
|
||||
self.assertEqual(type_validator["examples"], [None])
|
||||
|
||||
def test_null_parser_with_invalid_default(self):
|
||||
parser = NullTypeParser()
|
||||
|
||||
|
||||
@@ -1,9 +1,24 @@
|
||||
from jambo.exceptions import InternalAssertionException
|
||||
from jambo.parser import ObjectTypeParser
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestObjectTypeParser(TestCase):
|
||||
def test_object_type_parser_throws_without_ref_cache(self):
|
||||
parser = ObjectTypeParser()
|
||||
|
||||
properties = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(InternalAssertionException):
|
||||
parser.from_properties_impl("placeholder", properties)
|
||||
|
||||
def test_object_type_parser(self):
|
||||
parser = ObjectTypeParser()
|
||||
|
||||
@@ -15,13 +30,41 @@ class TestObjectTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
Model, _args = parser.from_properties_impl("placeholder", properties)
|
||||
Model, _args = parser.from_properties_impl(
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
obj = Model(name="name", age=10)
|
||||
|
||||
self.assertEqual(obj.name, "name")
|
||||
self.assertEqual(obj.age, 10)
|
||||
|
||||
def test_object_type_parser_with_object_example(self):
|
||||
parser = ObjectTypeParser()
|
||||
|
||||
properties = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
"examples": [
|
||||
{
|
||||
"name": "example_name",
|
||||
"age": 30,
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
_, type_validator = parser.from_properties_impl(
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
test_example = type_validator["examples"][0]
|
||||
|
||||
self.assertEqual(test_example.name, "example_name")
|
||||
self.assertEqual(test_example.age, 30)
|
||||
|
||||
def test_object_type_parser_with_default(self):
|
||||
parser = ObjectTypeParser()
|
||||
|
||||
@@ -37,7 +80,9 @@ class TestObjectTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
_, type_validator = parser.from_properties_impl("placeholder", properties)
|
||||
_, type_validator = parser.from_properties_impl(
|
||||
"placeholder", properties, ref_cache={}
|
||||
)
|
||||
|
||||
# Check default value
|
||||
default_obj = type_validator["default_factory"]()
|
||||
@@ -47,3 +92,18 @@ class TestObjectTypeParser(TestCase):
|
||||
# Chekc default factory new object id
|
||||
new_obj = type_validator["default_factory"]()
|
||||
self.assertNotEqual(id(default_obj), id(new_obj))
|
||||
|
||||
def test_object_type_parser_warns_if_object_override_in_cache(self):
|
||||
ref_cache = {}
|
||||
|
||||
parser = ObjectTypeParser()
|
||||
|
||||
properties = {"type": "object", "properties": {}}
|
||||
|
||||
with self.assertWarns(UserWarning):
|
||||
_, type_validator = parser.from_properties_impl(
|
||||
"placeholder", properties, ref_cache=ref_cache
|
||||
)
|
||||
_, type_validator = parser.from_properties_impl(
|
||||
"placeholder", properties, ref_cache=ref_cache
|
||||
)
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
from jambo import SchemaConverter
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser.oneof_type_parser import OneOfTypeParser
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
class TestOneOfTypeParser(TestCase):
|
||||
def test_oneof_raises_on_invalid_property(self):
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
OneOfTypeParser().from_properties_impl(
|
||||
"test_field",
|
||||
{
|
||||
@@ -17,7 +20,18 @@ class TestOneOfTypeParser(TestCase):
|
||||
ref_cache={},
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
OneOfTypeParser().from_properties_impl(
|
||||
"test_field",
|
||||
{
|
||||
"oneOf": [], # should throw because oneOf must be a list with at least one item
|
||||
},
|
||||
required=True,
|
||||
context={},
|
||||
ref_cache={},
|
||||
)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Test",
|
||||
@@ -71,13 +85,13 @@ class TestOneOfTypeParser(TestCase):
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id=-5)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id="invalid")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id=123.45)
|
||||
|
||||
def test_oneof_with_conflicting_schemas(self):
|
||||
@@ -103,11 +117,11 @@ class TestOneOfTypeParser(TestCase):
|
||||
obj2 = Model(data=9)
|
||||
self.assertEqual(obj2.data, 9)
|
||||
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
Model(data=6)
|
||||
self.assertIn("matches multiple oneOf schemas", str(cm.exception))
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(data=5)
|
||||
|
||||
def test_oneof_with_objects(self):
|
||||
@@ -147,7 +161,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
obj2 = Model(contact_info={"phone": "123-456-7890"})
|
||||
self.assertEqual(obj2.contact_info.phone, "123-456-7890")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(contact_info={"email": "user@example.com", "phone": "123-456-7890"})
|
||||
|
||||
def test_oneof_with_discriminator_basic(self):
|
||||
@@ -190,14 +204,14 @@ class TestOneOfTypeParser(TestCase):
|
||||
self.assertEqual(dog.pet.type, "dog")
|
||||
self.assertEqual(dog.pet.barks, False)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(pet={"type": "cat", "barks": True})
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(pet={"type": "bird", "flies": True})
|
||||
|
||||
def test_oneof_with_invalid_types(self):
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Pet",
|
||||
@@ -301,13 +315,13 @@ class TestOneOfTypeParser(TestCase):
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"type": "triangle", "base": 5, "height": 3})
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"type": "circle", "side": 5})
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"radius": 5})
|
||||
|
||||
def test_oneof_missing_properties(self):
|
||||
@@ -324,7 +338,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_oneof_invalid_properties(self):
|
||||
@@ -336,7 +350,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_oneof_with_default_value(self):
|
||||
@@ -373,12 +387,12 @@ class TestOneOfTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
def test_oneof_discriminator_without_property_name(self):
|
||||
# Should throw because the spec determines propertyName is required for discriminator
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Test",
|
||||
@@ -409,7 +423,7 @@ class TestOneOfTypeParser(TestCase):
|
||||
|
||||
def test_oneof_discriminator_with_invalid_discriminator(self):
|
||||
# Should throw because a valid discriminator is required
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
SchemaConverter.build(
|
||||
{
|
||||
"title": "Test",
|
||||
@@ -465,8 +479,9 @@ class TestOneOfTypeParser(TestCase):
|
||||
self.assertEqual(obj2.value, "very long string")
|
||||
|
||||
# Invalid: Medium string (matches BOTH schemas - violates oneOf)
|
||||
with self.assertRaises(ValueError) as cm:
|
||||
with self.assertRaises(ValidationError) as cm:
|
||||
Model(value="hello") # 5 chars: matches maxLength=6 AND minLength=4
|
||||
|
||||
self.assertIn("matches multiple oneOf schemas", str(cm.exception))
|
||||
|
||||
def test_oneof_shapes_discriminator_from_docs(self):
|
||||
@@ -515,5 +530,73 @@ class TestOneOfTypeParser(TestCase):
|
||||
self.assertEqual(rectangle.shape.height, 20)
|
||||
|
||||
# Invalid: Wrong properties for the type
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(shape={"type": "circle", "width": 10})
|
||||
|
||||
def test_oneof_with_examples(self):
|
||||
schema = {
|
||||
"title": "ExampleTest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"examples": ["example1", "example2"],
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"examples": [1, 2, 3],
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["value"],
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
|
||||
# Since Pydantic does not natively support oneOf and the validation
|
||||
# is done via a custom validator, the `value` is represented using `anyOf`
|
||||
model_schema = Model.model_json_schema()
|
||||
|
||||
self.assertEqual(
|
||||
model_schema["properties"]["value"]["anyOf"][0]["examples"],
|
||||
["example1", "example2"],
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
model_schema["properties"]["value"]["anyOf"][1]["examples"],
|
||||
[1, 2, 3],
|
||||
)
|
||||
|
||||
def test_oneof_with_root_examples(self):
|
||||
schema = {
|
||||
"title": "ExampleTest",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "string",
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
},
|
||||
],
|
||||
"examples": ["example1", 2],
|
||||
}
|
||||
},
|
||||
"required": ["value"],
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
|
||||
# Since Pydantic does not natively support oneOf and the validation
|
||||
# is done via a custom validator, the `value` is represented using `anyOf`
|
||||
model_schema = Model.model_json_schema()
|
||||
|
||||
self.assertEqual(
|
||||
model_schema["properties"]["value"]["examples"],
|
||||
["example1", 2],
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from jambo.exceptions import InternalAssertionException, InvalidSchemaException
|
||||
from jambo.parser import ObjectTypeParser, RefTypeParser
|
||||
|
||||
from typing import ForwardRef
|
||||
from pydantic import ValidationError
|
||||
from typing_extensions import ForwardRef
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
|
||||
@@ -16,7 +19,7 @@ class TestRefTypeParser(TestCase):
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -40,7 +43,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
with self.assertRaises(InternalAssertionException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -63,7 +66,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(RuntimeError):
|
||||
with self.assertRaises(InternalAssertionException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -77,7 +80,7 @@ class TestRefTypeParser(TestCase):
|
||||
"$ref": "https://example.com/schemas/person.json",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -110,7 +113,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
ObjectTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -126,7 +129,7 @@ class TestRefTypeParser(TestCase):
|
||||
"$defs": {},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -142,7 +145,7 @@ class TestRefTypeParser(TestCase):
|
||||
"$defs": {"person": None},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -232,7 +235,7 @@ class TestRefTypeParser(TestCase):
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
ObjectTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
@@ -264,7 +267,7 @@ class TestRefTypeParser(TestCase):
|
||||
)
|
||||
|
||||
# checks if when created via FowardRef the model is validated correctly.
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(
|
||||
name="John",
|
||||
age=30,
|
||||
@@ -421,7 +424,7 @@ class TestRefTypeParser(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
ref_strategy, ref_name, ref_property = RefTypeParser()._parse_from_strategy(
|
||||
"invalid_strategy",
|
||||
"person",
|
||||
@@ -482,3 +485,38 @@ class TestRefTypeParser(TestCase):
|
||||
|
||||
self.assertEqual(obj.name, "John")
|
||||
self.assertEqual(obj.age, 30)
|
||||
|
||||
def test_ref_type_parser_with_def_with_examples(self):
|
||||
properties = {
|
||||
"title": "person",
|
||||
"$ref": "#/$defs/person",
|
||||
"$defs": {
|
||||
"person": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
}
|
||||
},
|
||||
"examples": [
|
||||
{"name": "John", "age": 30},
|
||||
{"name": "Jane", "age": 25},
|
||||
],
|
||||
}
|
||||
|
||||
_, type_validator = RefTypeParser().from_properties(
|
||||
"person",
|
||||
properties,
|
||||
context=properties,
|
||||
ref_cache={},
|
||||
required=True,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
type_validator.get("examples"),
|
||||
[
|
||||
{"name": "John", "age": 30},
|
||||
{"name": "Jane", "age": 25},
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import StringTypeParser
|
||||
|
||||
from pydantic import AnyUrl, EmailStr
|
||||
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
import unittest
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from ipaddress import IPv4Address, IPv6Address, ip_address
|
||||
from unittest import TestCase
|
||||
from uuid import UUID
|
||||
|
||||
@@ -26,6 +28,7 @@ class TestStringTypeParser(TestCase):
|
||||
"maxLength": 10,
|
||||
"minLength": 1,
|
||||
"pattern": "^[a-zA-Z]+$",
|
||||
"examples": ["test", "TEST"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
@@ -34,6 +37,7 @@ class TestStringTypeParser(TestCase):
|
||||
self.assertEqual(type_validator["max_length"], 10)
|
||||
self.assertEqual(type_validator["min_length"], 1)
|
||||
self.assertEqual(type_validator["pattern"], "^[a-zA-Z]+$")
|
||||
self.assertEqual(type_validator["examples"], ["test", "TEST"])
|
||||
|
||||
def test_string_parser_with_default_value(self):
|
||||
parser = StringTypeParser()
|
||||
@@ -62,7 +66,7 @@ class TestStringTypeParser(TestCase):
|
||||
"minLength": 5,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_string_parser_with_default_invalid_maxlength(self):
|
||||
@@ -75,7 +79,7 @@ class TestStringTypeParser(TestCase):
|
||||
"minLength": 1,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_string_parser_with_default_invalid_minlength(self):
|
||||
@@ -88,7 +92,7 @@ class TestStringTypeParser(TestCase):
|
||||
"minLength": 2,
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
def test_string_parser_with_email_format(self):
|
||||
@@ -97,11 +101,13 @@ class TestStringTypeParser(TestCase):
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"examples": ["test@example.com"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, EmailStr)
|
||||
self.assertEqual(type_validator["examples"], ["test@example.com"])
|
||||
|
||||
def test_string_parser_with_uri_format(self):
|
||||
parser = StringTypeParser()
|
||||
@@ -109,21 +115,27 @@ class TestStringTypeParser(TestCase):
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"examples": ["test://domain/resource"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, AnyUrl)
|
||||
self.assertEqual(type_validator["examples"], ["test://domain/resource"])
|
||||
|
||||
def test_string_parser_with_ip_formats(self):
|
||||
parser = StringTypeParser()
|
||||
|
||||
formats = {"ipv4": IPv4Address, "ipv6": IPv6Address}
|
||||
examples = {"ipv4": ["192.168.1.1"], "ipv6": ["::1"]}
|
||||
|
||||
for ip_format, expected_type in formats.items():
|
||||
example = examples[ip_format]
|
||||
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": ip_format,
|
||||
"examples": example,
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties(
|
||||
@@ -131,6 +143,9 @@ class TestStringTypeParser(TestCase):
|
||||
)
|
||||
|
||||
self.assertEqual(type_parsing, expected_type)
|
||||
self.assertEqual(
|
||||
type_validator["examples"], [ip_address(e) for e in example]
|
||||
)
|
||||
|
||||
def test_string_parser_with_uuid_format(self):
|
||||
parser = StringTypeParser()
|
||||
@@ -138,11 +153,15 @@ class TestStringTypeParser(TestCase):
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"examples": ["ab71aaf4-ab6e-43cd-a369-cebdd9f7a4c6"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, UUID)
|
||||
self.assertEqual(
|
||||
type_validator["examples"], [UUID("ab71aaf4-ab6e-43cd-a369-cebdd9f7a4c6")]
|
||||
)
|
||||
|
||||
def test_string_parser_with_time_format(self):
|
||||
parser = StringTypeParser()
|
||||
@@ -150,19 +169,33 @@ class TestStringTypeParser(TestCase):
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "time",
|
||||
"examples": ["14:30:00", "09:15:30.500", "10:00:00+02:00"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, time)
|
||||
self.assertEqual(
|
||||
type_validator["examples"],
|
||||
[
|
||||
time(hour=14, minute=30, second=0),
|
||||
time(hour=9, minute=15, second=30, microsecond=500_000),
|
||||
time(hour=10, minute=0, second=0, tzinfo=timezone(timedelta(hours=2))),
|
||||
],
|
||||
)
|
||||
|
||||
def test_string_parser_with_pattern_based_formats(self):
|
||||
parser = StringTypeParser()
|
||||
|
||||
for format_type in ["hostname"]:
|
||||
format_types = {
|
||||
"hostname": "example.com",
|
||||
}
|
||||
|
||||
for format_type, example_type in format_types.items():
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": format_type,
|
||||
"examples": [example_type],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties(
|
||||
@@ -174,6 +207,7 @@ class TestStringTypeParser(TestCase):
|
||||
self.assertEqual(
|
||||
type_validator["pattern"], parser.format_pattern_mapping[format_type]
|
||||
)
|
||||
self.assertEqual(type_validator["examples"], [example_type])
|
||||
|
||||
def test_string_parser_with_unsupported_format(self):
|
||||
parser = StringTypeParser()
|
||||
@@ -183,11 +217,12 @@ class TestStringTypeParser(TestCase):
|
||||
"format": "unsupported-format",
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(
|
||||
str(context.exception), "Unsupported string format: unsupported-format"
|
||||
str(context.exception),
|
||||
"Invalid JSON Schema: Unsupported string format: unsupported-format (invalid field: format)",
|
||||
)
|
||||
|
||||
def test_string_parser_with_date_format(self):
|
||||
@@ -196,11 +231,20 @@ class TestStringTypeParser(TestCase):
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "date",
|
||||
"examples": ["2025-11-17", "1999-12-31", "2000-01-01"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, date)
|
||||
self.assertEqual(
|
||||
type_validator["examples"],
|
||||
[
|
||||
date(year=2025, month=11, day=17),
|
||||
date(year=1999, month=12, day=31),
|
||||
date(year=2000, month=1, day=1),
|
||||
],
|
||||
)
|
||||
|
||||
def test_string_parser_with_datetime_format(self):
|
||||
parser = StringTypeParser()
|
||||
@@ -208,20 +252,72 @@ class TestStringTypeParser(TestCase):
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"examples": [
|
||||
"2025-11-17T11:15:00",
|
||||
"2025-11-17T11:15:00+01:00",
|
||||
"2025-11-17T11:15:00.123456-05:00",
|
||||
],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, datetime)
|
||||
self.assertEqual(
|
||||
type_validator["examples"],
|
||||
[
|
||||
datetime(year=2025, month=11, day=17, hour=11, minute=15, second=0),
|
||||
datetime(
|
||||
year=2025,
|
||||
month=11,
|
||||
day=17,
|
||||
hour=11,
|
||||
minute=15,
|
||||
second=0,
|
||||
tzinfo=timezone(timedelta(hours=1)),
|
||||
),
|
||||
datetime(
|
||||
year=2025,
|
||||
month=11,
|
||||
day=17,
|
||||
hour=11,
|
||||
minute=15,
|
||||
second=0,
|
||||
microsecond=123456,
|
||||
tzinfo=timezone(timedelta(hours=-5)),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
def test_string_parser_with_invalid_example_value(self):
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
StringTypeParser().from_properties(
|
||||
"placeholder",
|
||||
{
|
||||
"type": "string",
|
||||
"format": "email",
|
||||
"examples": ["invalid-email"],
|
||||
},
|
||||
)
|
||||
|
||||
@unittest.skip("Duration parsing not yet implemented")
|
||||
def test_string_parser_with_timedelta_format(self):
|
||||
parser = StringTypeParser()
|
||||
|
||||
properties = {
|
||||
"type": "string",
|
||||
"format": "duration",
|
||||
"examples": ["P1Y2M3DT4H5M6S", "PT30M", "P7D", "PT0.5S"],
|
||||
}
|
||||
|
||||
type_parsing, type_validator = parser.from_properties("placeholder", properties)
|
||||
|
||||
self.assertEqual(type_parsing, timedelta)
|
||||
self.assertEqual(
|
||||
type_validator["examples"],
|
||||
[
|
||||
timedelta(days=7),
|
||||
timedelta(minutes=30),
|
||||
timedelta(hours=4, minutes=5, seconds=6),
|
||||
timedelta(seconds=0.5),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from jambo.exceptions import InvalidSchemaException
|
||||
from jambo.parser import StringTypeParser
|
||||
from jambo.parser._type_parser import GenericTypeParser
|
||||
|
||||
@@ -17,5 +18,16 @@ class TestGenericTypeParser(TestCase):
|
||||
StringTypeParser.json_schema_type = "type:string"
|
||||
|
||||
def test_get_impl_invalid_type(self):
|
||||
with self.assertRaises(ValueError):
|
||||
GenericTypeParser._get_impl({"type": "invalid_type"})
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
GenericTypeParser._get_impl({"type": "invalid_type"})
|
||||
|
||||
def test_invalid_examples_not_list(self):
|
||||
parser = StringTypeParser()
|
||||
|
||||
properties = {
|
||||
"type": "integer",
|
||||
"examples": "this should be a list",
|
||||
}
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
parser.from_properties("placeholder", properties)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from jambo import SchemaConverter
|
||||
from jambo.exceptions import InvalidSchemaException, UnsupportedSchemaException
|
||||
from jambo.types import JSONSchema
|
||||
|
||||
from pydantic import AnyUrl, BaseModel
|
||||
from pydantic import AnyUrl, BaseModel, ValidationError
|
||||
from typing_extensions import get_args
|
||||
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from unittest import TestCase
|
||||
@@ -12,6 +15,12 @@ def is_pydantic_model(cls):
|
||||
|
||||
|
||||
class TestSchemaConverter(TestCase):
|
||||
def setUp(self):
|
||||
self.converter = SchemaConverter()
|
||||
|
||||
def tearDown(self):
|
||||
self.converter.clear_ref_cache(namespace=None)
|
||||
|
||||
def test_invalid_schema(self):
|
||||
schema = {
|
||||
"title": 1,
|
||||
@@ -23,8 +32,22 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
SchemaConverter.build(schema)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def test_invalid_schema_type(self):
|
||||
schema = {
|
||||
"title": 1,
|
||||
"description": "A person",
|
||||
"type": 1,
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def test_build_expects_title(self):
|
||||
schema = {
|
||||
@@ -36,8 +59,8 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
SchemaConverter.build(schema)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def test_build_expects_object(self):
|
||||
schema = {
|
||||
@@ -46,8 +69,8 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "string",
|
||||
}
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
SchemaConverter.build(schema)
|
||||
with self.assertRaises(UnsupportedSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def test_is_invalid_field(self):
|
||||
schema = {
|
||||
@@ -62,8 +85,8 @@ class TestSchemaConverter(TestCase):
|
||||
# 'required': ['name', 'age', 'is_active', 'friends', 'address'],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError) as context:
|
||||
SchemaConverter.build(schema)
|
||||
with self.assertRaises(InvalidSchemaException) as context:
|
||||
self.converter.build_with_cache(schema)
|
||||
self.assertTrue("Unknown type" in str(context.exception))
|
||||
|
||||
def test_jsonschema_to_pydantic(self):
|
||||
@@ -78,7 +101,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertTrue(is_pydantic_model(model))
|
||||
|
||||
@@ -99,20 +122,20 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertEqual(model(name="John", age=30).name, "John")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name=123, age=30, email="teste@hideyoshi.com")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name="John Invalid", age=45, email="teste@hideyoshi.com")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name="", age=45, email="teste@hideyoshi.com")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(name="John", age=45, email="hideyoshi.com")
|
||||
|
||||
def test_validation_integer(self):
|
||||
@@ -130,14 +153,14 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["age"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertEqual(model(age=30).age, 30)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=-1)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=121)
|
||||
|
||||
def test_validation_float(self):
|
||||
@@ -155,14 +178,14 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["age"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertEqual(model(age=30).age, 30.0)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=-1.0)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(age=121.0)
|
||||
|
||||
def test_validation_boolean(self):
|
||||
@@ -176,7 +199,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["is_active"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertEqual(model(is_active=True).is_active, True)
|
||||
|
||||
@@ -199,39 +222,20 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["friends"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertEqual(
|
||||
model(friends=["John", "Jane", "John"]).friends, {"John", "Jane"}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(friends=[])
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(friends=["John", "Jane", "Invalid"])
|
||||
|
||||
def test_validation_list_with_missing_items(self):
|
||||
model = SchemaConverter.build(
|
||||
{
|
||||
"title": "Person",
|
||||
"description": "A person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"friends": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"minItems": 1,
|
||||
"maxItems": 2,
|
||||
"default": ["John", "Jane"],
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(model().friends, ["John", "Jane"])
|
||||
|
||||
model = SchemaConverter.build(
|
||||
model = self.converter.build_with_cache(
|
||||
{
|
||||
"title": "Person",
|
||||
"description": "A person",
|
||||
@@ -248,7 +252,7 @@ class TestSchemaConverter(TestCase):
|
||||
}
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model()
|
||||
|
||||
def test_validation_object(self):
|
||||
@@ -269,14 +273,14 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["address"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = model(address={"street": "123 Main St", "city": "Springfield"})
|
||||
|
||||
self.assertEqual(obj.address.street, "123 Main St")
|
||||
self.assertEqual(obj.address.city, "Springfield")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model()
|
||||
|
||||
def test_default_for_string(self):
|
||||
@@ -293,7 +297,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = model(name="John")
|
||||
|
||||
@@ -315,8 +319,8 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
SchemaConverter.build(schema_max_length)
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema_max_length)
|
||||
|
||||
def test_default_for_list(self):
|
||||
schema_list = {
|
||||
@@ -333,10 +337,11 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["friends"],
|
||||
}
|
||||
|
||||
model_list = SchemaConverter.build(schema_list)
|
||||
model_list = self.converter.build_with_cache(schema_list)
|
||||
|
||||
self.assertEqual(model_list().friends, ["John", "Jane"])
|
||||
|
||||
def test_default_for_list_with_unique_items(self):
|
||||
# Test for default with uniqueItems
|
||||
schema_set = {
|
||||
"title": "Person",
|
||||
@@ -353,7 +358,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["friends"],
|
||||
}
|
||||
|
||||
model_set = SchemaConverter.build(schema_set)
|
||||
model_set = self.converter.build_with_cache(schema_set)
|
||||
|
||||
self.assertEqual(model_set().friends, {"John", "Jane"})
|
||||
|
||||
@@ -375,7 +380,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["address"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = model(address={"street": "123 Main St", "city": "Springfield"})
|
||||
|
||||
@@ -399,7 +404,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model(
|
||||
name="J",
|
||||
@@ -407,10 +412,10 @@ class TestSchemaConverter(TestCase):
|
||||
|
||||
self.assertEqual(obj.name, "J")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name="John Invalid")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name="")
|
||||
|
||||
def test_any_of(self):
|
||||
@@ -428,7 +433,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model(id=1)
|
||||
self.assertEqual(obj.id, 1)
|
||||
@@ -436,13 +441,13 @@ class TestSchemaConverter(TestCase):
|
||||
obj = Model(id="12345678901")
|
||||
self.assertEqual(obj.id, "12345678901")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id="")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id="12345678901234567890")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(id=11)
|
||||
|
||||
def test_string_format_email(self):
|
||||
@@ -451,9 +456,11 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"email": {"type": "string", "format": "email"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(model(email="test@example.com").email, "test@example.com")
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(email="invalid-email")
|
||||
|
||||
def test_string_format_uri(self):
|
||||
@@ -462,11 +469,13 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"website": {"type": "string", "format": "uri"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(
|
||||
model(website="https://example.com").website, AnyUrl("https://example.com")
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(website="invalid-uri")
|
||||
|
||||
def test_string_format_ipv4(self):
|
||||
@@ -475,9 +484,11 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"ip": {"type": "string", "format": "ipv4"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(model(ip="192.168.1.1").ip, IPv4Address("192.168.1.1"))
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(ip="256.256.256.256")
|
||||
|
||||
def test_string_format_ipv6(self):
|
||||
@@ -486,12 +497,14 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"ip": {"type": "string", "format": "ipv6"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(
|
||||
model(ip="2001:0db8:85a3:0000:0000:8a2e:0370:7334").ip,
|
||||
IPv6Address("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(ip="invalid-ipv6")
|
||||
|
||||
def test_string_format_uuid(self):
|
||||
@@ -500,14 +513,15 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"id": {"type": "string", "format": "uuid"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
self.assertEqual(
|
||||
model(id="123e4567-e89b-12d3-a456-426614174000").id,
|
||||
UUID("123e4567-e89b-12d3-a456-426614174000"),
|
||||
)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
model(id="invalid-uuid")
|
||||
|
||||
def test_string_format_hostname(self):
|
||||
@@ -516,9 +530,11 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"hostname": {"type": "string", "format": "hostname"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(model(hostname="example.com").hostname, "example.com")
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(hostname="invalid..hostname")
|
||||
|
||||
def test_string_format_datetime(self):
|
||||
@@ -527,12 +543,14 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"timestamp": {"type": "string", "format": "date-time"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(
|
||||
model(timestamp="2024-01-01T12:00:00Z").timestamp.isoformat(),
|
||||
"2024-01-01T12:00:00+00:00",
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(timestamp="invalid-datetime")
|
||||
|
||||
def test_string_format_time(self):
|
||||
@@ -541,11 +559,13 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"time": {"type": "string", "format": "time"}},
|
||||
}
|
||||
model = SchemaConverter.build(schema)
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
self.assertEqual(
|
||||
model(time="20:20:39+00:00").time.isoformat(), "20:20:39+00:00"
|
||||
)
|
||||
with self.assertRaises(ValueError):
|
||||
|
||||
with self.assertRaises(ValidationError):
|
||||
model(time="25:00:00")
|
||||
|
||||
def test_string_format_unsupported(self):
|
||||
@@ -554,8 +574,9 @@ class TestSchemaConverter(TestCase):
|
||||
"type": "object",
|
||||
"properties": {"field": {"type": "string", "format": "unsupported"}},
|
||||
}
|
||||
with self.assertRaises(ValueError):
|
||||
SchemaConverter.build(schema)
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def test_ref_with_root_ref(self):
|
||||
schema = {
|
||||
@@ -571,7 +592,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = model(
|
||||
name="John",
|
||||
@@ -606,7 +627,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
model = SchemaConverter.build(schema)
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = model(
|
||||
name="John",
|
||||
@@ -645,7 +666,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model(
|
||||
name="John",
|
||||
@@ -671,7 +692,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["status"],
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model(status="active")
|
||||
self.assertEqual(obj.status.value, "active")
|
||||
@@ -690,7 +711,7 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["status"],
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model()
|
||||
self.assertEqual(obj.status.value, "active")
|
||||
@@ -707,15 +728,15 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model()
|
||||
self.assertEqual(obj.name, "United States of America")
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
obj.name = "Canada"
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name="Canada")
|
||||
|
||||
def test_const_type_parser_with_non_hashable_value(self):
|
||||
@@ -730,15 +751,15 @@ class TestSchemaConverter(TestCase):
|
||||
"required": ["name"],
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model()
|
||||
self.assertEqual(obj.name, ["Brazil"])
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
obj.name = ["Argentina"]
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(name=["Argentina"])
|
||||
|
||||
def test_null_type_parser(self):
|
||||
@@ -750,7 +771,7 @@ class TestSchemaConverter(TestCase):
|
||||
},
|
||||
}
|
||||
|
||||
Model = SchemaConverter.build(schema)
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj = Model()
|
||||
self.assertIsNone(obj.a_thing)
|
||||
@@ -758,5 +779,392 @@ class TestSchemaConverter(TestCase):
|
||||
obj = Model(a_thing=None)
|
||||
self.assertIsNone(obj.a_thing)
|
||||
|
||||
with self.assertRaises(ValueError):
|
||||
with self.assertRaises(ValidationError):
|
||||
Model(a_thing="not none")
|
||||
|
||||
def test_scoped_ref_schema(self):
|
||||
schema: JSONSchema = {
|
||||
"title": "Example Schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"operating_system": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/$defs/operating_system"},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"creation": {"$ref": "#/$defs/operating_system"},
|
||||
"reinstallation": {"$ref": "#/$defs/operating_system"},
|
||||
},
|
||||
"required": ["creation", "reinstallation"],
|
||||
},
|
||||
]
|
||||
},
|
||||
},
|
||||
"$defs": {
|
||||
"operating_system": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"version": {"type": "string"},
|
||||
},
|
||||
"required": ["name", "version"],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
schema_type = self.converter.build_with_cache(schema)
|
||||
|
||||
# check for me that the types generated by the oneOf in the typing.Annotated have different names
|
||||
operating_system_field = schema_type.model_fields["operating_system"]
|
||||
|
||||
arg1, arg2 = get_args(operating_system_field.annotation)
|
||||
|
||||
first_type = get_args(arg1)[0]
|
||||
second_type = get_args(arg2)[0]
|
||||
|
||||
self.assertNotEqual(first_type.__name__, second_type.__name__)
|
||||
|
||||
def test_object_invalid_require(self):
|
||||
# https://github.com/HideyoshiNakazone/jambo/issues/60
|
||||
object_ = self.converter.build_with_cache(
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"title": "TEST",
|
||||
"type": "object",
|
||||
"required": ["title"],
|
||||
"properties": {
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "The title of the object",
|
||||
},
|
||||
"description": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"summary": {
|
||||
"type": "string",
|
||||
},
|
||||
"details": {
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
self.assertFalse(object_.model_fields["description"].is_required()) # FAIL
|
||||
|
||||
def test_instance_level_ref_cache(self):
|
||||
ref_cache = {}
|
||||
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"emergency_contact": {
|
||||
"$ref": "#",
|
||||
},
|
||||
},
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
converter1 = SchemaConverter(ref_cache)
|
||||
model1 = converter1.build_with_cache(schema)
|
||||
|
||||
converter2 = SchemaConverter(ref_cache)
|
||||
model2 = converter2.build_with_cache(schema)
|
||||
|
||||
self.assertIs(model1, model2)
|
||||
|
||||
def test_instance_level_ref_cache_isolation_via_without_cache_param(self):
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"emergency_contact": {
|
||||
"$ref": "#",
|
||||
},
|
||||
},
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
model1 = self.converter.build_with_cache(schema, without_cache=True)
|
||||
model2 = self.converter.build_with_cache(schema, without_cache=True)
|
||||
|
||||
self.assertIsNot(model1, model2)
|
||||
|
||||
def test_instance_level_ref_cache_isolation_via_provided_cache(self):
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"emergency_contact": {
|
||||
"$ref": "#",
|
||||
},
|
||||
},
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
model1 = self.converter.build_with_cache(schema, ref_cache={})
|
||||
model2 = self.converter.build_with_cache(schema, ref_cache={})
|
||||
|
||||
self.assertIsNot(model1, model2)
|
||||
|
||||
def test_get_type_from_cache(self):
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"emergency_contact": {
|
||||
"$ref": "#",
|
||||
},
|
||||
},
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
cached_model = self.converter.get_cached_ref("Person")
|
||||
|
||||
self.assertIs(model, cached_model)
|
||||
|
||||
def test_get_type_from_cache_not_found(self):
|
||||
cached_model = self.converter.get_cached_ref("NonExistentModel")
|
||||
|
||||
self.assertIsNone(cached_model)
|
||||
|
||||
def test_get_type_from_cache_nested_type(self):
|
||||
schema = {
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
},
|
||||
"required": ["name", "age", "address"],
|
||||
}
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
cached_model = self.converter.get_cached_ref("Person.address")
|
||||
|
||||
self.assertIsNotNone(cached_model)
|
||||
self.assertIs(model.model_fields["address"].annotation, cached_model)
|
||||
|
||||
def test_get_type_from_cache_with_def(self):
|
||||
schema = {
|
||||
"title": "person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {"$ref": "#/$defs/address"},
|
||||
},
|
||||
"$defs": {
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
person_model = self.converter.build_with_cache(schema)
|
||||
cached_person_model = self.converter.get_cached_ref("person")
|
||||
|
||||
self.assertIs(person_model, cached_person_model)
|
||||
|
||||
cached_address_model = self.converter.get_cached_ref("address")
|
||||
|
||||
self.assertIsNotNone(cached_address_model)
|
||||
|
||||
def test_parse_list_type_multiple_values(self):
|
||||
schema = {
|
||||
"title": "TestListType",
|
||||
"type": "object",
|
||||
"properties": {"values": {"type": ["string", "number"]}},
|
||||
}
|
||||
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj1 = Model(values="a string")
|
||||
self.assertEqual(obj1.values, "a string")
|
||||
|
||||
obj2 = Model(values=42)
|
||||
self.assertEqual(obj2.values, 42)
|
||||
|
||||
def test_parse_list_type_one_value(self):
|
||||
schema = {
|
||||
"title": "TestListType",
|
||||
"type": "object",
|
||||
"properties": {"values": {"type": ["string"]}},
|
||||
}
|
||||
|
||||
Model = self.converter.build_with_cache(schema)
|
||||
|
||||
obj1 = Model(values="a string")
|
||||
self.assertEqual(obj1.values, "a string")
|
||||
|
||||
def test_parse_list_type_empty(self):
|
||||
schema = {
|
||||
"title": "TestListType",
|
||||
"type": "object",
|
||||
"properties": {"values": {"type": []}},
|
||||
}
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def test_parse_list_type_root_level_throws(self):
|
||||
schema = {"title": "TestListType", "type": ["string", "number"]}
|
||||
|
||||
with self.assertRaises(InvalidSchemaException):
|
||||
self.converter.build_with_cache(schema)
|
||||
|
||||
def tests_instance_level_ref_cache_isolation_via_property_id(self):
|
||||
schema1: JSONSchema = {
|
||||
"$id": "http://example.com/schemas/person1.json",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"emergency_contact": {
|
||||
"$ref": "#",
|
||||
},
|
||||
},
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
model1 = self.converter.build_with_cache(schema1)
|
||||
|
||||
schema2: JSONSchema = {
|
||||
"$id": "http://example.com/schemas/person2.json",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {"type": "string"},
|
||||
},
|
||||
"required": ["name", "age", "address"],
|
||||
}
|
||||
|
||||
model2 = self.converter.build_with_cache(schema2)
|
||||
|
||||
self.assertIsNot(model1, model2)
|
||||
|
||||
def tests_instance_level_ref_cache_colision_when_same_property_id(self):
|
||||
schema1: JSONSchema = {
|
||||
"$id": "http://example.com/schemas/person.json",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"emergency_contact": {
|
||||
"$ref": "#",
|
||||
},
|
||||
},
|
||||
"required": ["name", "age"],
|
||||
}
|
||||
|
||||
model1 = self.converter.build_with_cache(schema1)
|
||||
|
||||
schema2: JSONSchema = {
|
||||
"$id": "http://example.com/schemas/person.json",
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {"type": "string"},
|
||||
},
|
||||
"required": ["name", "age", "address"],
|
||||
}
|
||||
|
||||
model2 = self.converter.build_with_cache(schema2)
|
||||
|
||||
self.assertIs(model1, model2)
|
||||
|
||||
def test_namespace_isolation_via_on_call_config(self):
|
||||
namespace = "namespace1"
|
||||
|
||||
schema: JSONSchema = {
|
||||
"$id": namespace,
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
},
|
||||
"required": ["name", "age", "address"],
|
||||
}
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
invalid_cached_model = self.converter.get_cached_ref("Person")
|
||||
self.assertIsNone(invalid_cached_model)
|
||||
|
||||
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||
self.assertIs(model, cached_model)
|
||||
|
||||
def test_clear_namespace_registry(self):
|
||||
namespace = "namespace_to_clear"
|
||||
|
||||
schema: JSONSchema = {
|
||||
"$id": namespace,
|
||||
"title": "Person",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"age": {"type": "integer"},
|
||||
"address": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"street": {"type": "string"},
|
||||
"city": {"type": "string"},
|
||||
},
|
||||
"required": ["street", "city"],
|
||||
},
|
||||
},
|
||||
"required": ["name", "age", "address"],
|
||||
}
|
||||
|
||||
model = self.converter.build_with_cache(schema)
|
||||
|
||||
cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||
self.assertIs(model, cached_model)
|
||||
|
||||
self.converter.clear_ref_cache(namespace=namespace)
|
||||
|
||||
cleared_cached_model = self.converter.get_cached_ref("Person", namespace=namespace)
|
||||
self.assertIsNone(cleared_cached_model)
|
||||
|
||||
433
uv.lock
generated
433
uv.lock
generated
@@ -259,14 +259,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -326,31 +326,39 @@ dependencies = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "coverage" },
|
||||
{ name = "mypy" },
|
||||
{ name = "poethepoet" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "ruff" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinx-autobuild" },
|
||||
{ name = "sphinx-autobuild", version = "2024.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinx-autobuild", version = "2025.8.25", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinx-autodoc-typehints", version = "3.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinx-autodoc-typehints", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinx-rtd-theme" },
|
||||
{ name = "types-jsonschema" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "email-validator", specifier = ">=2.2.0" },
|
||||
{ name = "jsonschema", specifier = ">=4.23.0" },
|
||||
{ name = "pydantic", specifier = ">=2.10.6" },
|
||||
{ name = "pydantic", specifier = ">=2.12.4" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "coverage", specifier = ">=7.8.0" },
|
||||
{ name = "mypy", specifier = ">=1.18.1" },
|
||||
{ name = "poethepoet", specifier = ">=0.33.1" },
|
||||
{ name = "pre-commit", specifier = ">=4.2.0" },
|
||||
{ name = "ruff", specifier = ">=0.11.4" },
|
||||
{ name = "sphinx", specifier = ">=8.1.3" },
|
||||
{ name = "sphinx-autobuild", specifier = ">=2024.10.3" },
|
||||
{ name = "sphinx-autodoc-typehints", specifier = ">=3.0.1" },
|
||||
{ name = "sphinx-rtd-theme", specifier = ">=3.0.2" },
|
||||
{ name = "types-jsonschema", specifier = ">=4.25.1.20250822" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -450,6 +458,60 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739, upload-time = "2024-10-18T15:21:42.784Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.18.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mypy-extensions" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/14/a3/931e09fc02d7ba96da65266884da4e4a8806adcdb8a57faaacc6edf1d538/mypy-1.18.1.tar.gz", hash = "sha256:9e988c64ad3ac5987f43f5154f884747faf62141b7f842e87465b45299eea5a9", size = 3448447, upload-time = "2025-09-11T23:00:47.067Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/06/29ea5a34c23938ae93bc0040eb2900eb3f0f2ef4448cc59af37ab3ddae73/mypy-1.18.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2761b6ae22a2b7d8e8607fb9b81ae90bc2e95ec033fd18fa35e807af6c657763", size = 12811535, upload-time = "2025-09-11T22:58:55.399Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/40/04c38cb04fa9f1dc224b3e9634021a92c47b1569f1c87dfe6e63168883bb/mypy-1.18.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5b10e3ea7f2eec23b4929a3fabf84505da21034a4f4b9613cda81217e92b74f3", size = 11897559, upload-time = "2025-09-11T22:59:48.041Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/bf/4c535bd45ea86cebbc1a3b6a781d442f53a4883f322ebd2d442db6444d0b/mypy-1.18.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:261fbfced030228bc0f724d5d92f9ae69f46373bdfd0e04a533852677a11dbea", size = 12507430, upload-time = "2025-09-11T22:59:30.415Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/e1/cbefb16f2be078d09e28e0b9844e981afb41f6ffc85beb68b86c6976e641/mypy-1.18.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4dc6b34a1c6875e6286e27d836a35c0d04e8316beac4482d42cfea7ed2527df8", size = 13243717, upload-time = "2025-09-11T22:59:11.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/65/e8/3e963da63176f16ca9caea7fa48f1bc8766de317cd961528c0391565fd47/mypy-1.18.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1cabb353194d2942522546501c0ff75c4043bf3b63069cb43274491b44b773c9", size = 13492052, upload-time = "2025-09-11T23:00:09.29Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4b/09/d5d70c252a3b5b7530662d145437bd1de15f39fa0b48a27ee4e57d254aa1/mypy-1.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:738b171690c8e47c93569635ee8ec633d2cdb06062f510b853b5f233020569a9", size = 9765846, upload-time = "2025-09-11T22:58:26.198Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/28/47709d5d9e7068b26c0d5189c8137c8783e81065ad1102b505214a08b548/mypy-1.18.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6c903857b3e28fc5489e54042684a9509039ea0aedb2a619469438b544ae1961", size = 12734635, upload-time = "2025-09-11T23:00:24.983Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/12/ee5c243e52497d0e59316854041cf3b3130131b92266d0764aca4dec3c00/mypy-1.18.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2a0c8392c19934c2b6c65566d3a6abdc6b51d5da7f5d04e43f0eb627d6eeee65", size = 11817287, upload-time = "2025-09-11T22:59:07.38Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/bd/2aeb950151005fe708ab59725afed7c4aeeb96daf844f86a05d4b8ac34f8/mypy-1.18.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f85eb7efa2ec73ef63fc23b8af89c2fe5bf2a4ad985ed2d3ff28c1bb3c317c92", size = 12430464, upload-time = "2025-09-11T22:58:48.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/e8/7a20407aafb488acb5734ad7fb5e8c2ef78d292ca2674335350fa8ebef67/mypy-1.18.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:82ace21edf7ba8af31c3308a61dc72df30500f4dbb26f99ac36b4b80809d7e94", size = 13164555, upload-time = "2025-09-11T23:00:13.803Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/c9/5f39065252e033b60f397096f538fb57c1d9fd70a7a490f314df20dd9d64/mypy-1.18.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a2dfd53dfe632f1ef5d161150a4b1f2d0786746ae02950eb3ac108964ee2975a", size = 13359222, upload-time = "2025-09-11T23:00:33.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/85/b6/d54111ef3c1e55992cd2ec9b8b6ce9c72a407423e93132cae209f7e7ba60/mypy-1.18.1-cp311-cp311-win_amd64.whl", hash = "sha256:320f0ad4205eefcb0e1a72428dde0ad10be73da9f92e793c36228e8ebf7298c0", size = 9760441, upload-time = "2025-09-11T23:00:44.826Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/14/1c3f54d606cb88a55d1567153ef3a8bc7b74702f2ff5eb64d0994f9e49cb/mypy-1.18.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:502cde8896be8e638588b90fdcb4c5d5b8c1b004dfc63fd5604a973547367bb9", size = 12911082, upload-time = "2025-09-11T23:00:41.465Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/90/83/235606c8b6d50a8eba99773add907ce1d41c068edb523f81eb0d01603a83/mypy-1.18.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7509549b5e41be279afc1228242d0e397f1af2919a8f2877ad542b199dc4083e", size = 11919107, upload-time = "2025-09-11T22:58:40.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/25/4e2ce00f8d15b99d0c68a2536ad63e9eac033f723439ef80290ec32c1ff5/mypy-1.18.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5956ecaabb3a245e3f34100172abca1507be687377fe20e24d6a7557e07080e2", size = 12472551, upload-time = "2025-09-11T22:58:37.272Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/bb/92642a9350fc339dd9dcefcf6862d171b52294af107d521dce075f32f298/mypy-1.18.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8750ceb014a96c9890421c83f0db53b0f3b8633e2864c6f9bc0a8e93951ed18d", size = 13340554, upload-time = "2025-09-11T22:59:38.756Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/ee/38d01db91c198fb6350025d28f9719ecf3c8f2c55a0094bfbf3ef478cc9a/mypy-1.18.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb89ea08ff41adf59476b235293679a6eb53a7b9400f6256272fb6029bec3ce5", size = 13530933, upload-time = "2025-09-11T22:59:20.228Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/8d/6d991ae631f80d58edbf9d7066e3f2a96e479dca955d9a968cd6e90850a3/mypy-1.18.1-cp312-cp312-win_amd64.whl", hash = "sha256:2657654d82fcd2a87e02a33e0d23001789a554059bbf34702d623dafe353eabf", size = 9828426, upload-time = "2025-09-11T23:00:21.007Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ec/ef4a7260e1460a3071628a9277a7579e7da1b071bc134ebe909323f2fbc7/mypy-1.18.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70d2b5baf9b9a20bc9c730015615ae3243ef47fb4a58ad7b31c3e0a59b5ef1f", size = 12918671, upload-time = "2025-09-11T22:58:29.814Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/82/0ea6c3953f16223f0b8eda40c1aeac6bd266d15f4902556ae6e91f6fca4c/mypy-1.18.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8367e33506300f07a43012fc546402f283c3f8bcff1dc338636affb710154ce", size = 11913023, upload-time = "2025-09-11T23:00:29.049Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/ef/5e2057e692c2690fc27b3ed0a4dbde4388330c32e2576a23f0302bc8358d/mypy-1.18.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:913f668ec50c3337b89df22f973c1c8f0b29ee9e290a8b7fe01cc1ef7446d42e", size = 12473355, upload-time = "2025-09-11T23:00:04.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/43/b7e429fc4be10e390a167b0cd1810d41cb4e4add4ae50bab96faff695a3b/mypy-1.18.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a0e70b87eb27b33209fa4792b051c6947976f6ab829daa83819df5f58330c71", size = 13346944, upload-time = "2025-09-11T22:58:23.024Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/4e/899dba0bfe36bbd5b7c52e597de4cf47b5053d337b6d201a30e3798e77a6/mypy-1.18.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c378d946e8a60be6b6ede48c878d145546fb42aad61df998c056ec151bf6c746", size = 13512574, upload-time = "2025-09-11T22:59:52.152Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/f8/7661021a5b0e501b76440454d786b0f01bb05d5c4b125fcbda02023d0250/mypy-1.18.1-cp313-cp313-win_amd64.whl", hash = "sha256:2cd2c1e0f3a7465f22731987fff6fc427e3dcbb4ca5f7db5bbeaff2ff9a31f6d", size = 9837684, upload-time = "2025-09-11T22:58:44.454Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/87/7b173981466219eccc64c107cf8e5ab9eb39cc304b4c07df8e7881533e4f/mypy-1.18.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ba24603c58e34dd5b096dfad792d87b304fc6470cbb1c22fd64e7ebd17edcc61", size = 12900265, upload-time = "2025-09-11T22:59:03.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/cc/b10e65bae75b18a5ac8f81b1e8e5867677e418f0dd2c83b8e2de9ba96ebd/mypy-1.18.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ed36662fb92ae4cb3cacc682ec6656208f323bbc23d4b08d091eecfc0863d4b5", size = 11942890, upload-time = "2025-09-11T23:00:00.607Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/d4/aeefa07c44d09f4c2102e525e2031bc066d12e5351f66b8a83719671004d/mypy-1.18.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:040ecc95e026f71a9ad7956fea2724466602b561e6a25c2e5584160d3833aaa8", size = 12472291, upload-time = "2025-09-11T22:59:43.425Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/07/711e78668ff8e365f8c19735594ea95938bff3639a4c46a905e3ed8ff2d6/mypy-1.18.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:937e3ed86cb731276706e46e03512547e43c391a13f363e08d0fee49a7c38a0d", size = 13318610, upload-time = "2025-09-11T23:00:17.604Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/85/df3b2d39339c31d360ce299b418c55e8194ef3205284739b64962f6074e7/mypy-1.18.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f95cc4f01c0f1701ca3b0355792bccec13ecb2ec1c469e5b85a6ef398398b1d", size = 13513697, upload-time = "2025-09-11T22:58:59.534Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/df/462866163c99ea73bb28f0eb4d415c087e30de5d36ee0f5429d42e28689b/mypy-1.18.1-cp314-cp314-win_amd64.whl", hash = "sha256:e4f16c0019d48941220ac60b893615be2f63afedaba6a0801bdcd041b96991ce", size = 9985739, upload-time = "2025-09-11T22:58:51.644Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/1d/4b97d3089b48ef3d904c9ca69fab044475bd03245d878f5f0b3ea1daf7ce/mypy-1.18.1-py3-none-any.whl", hash = "sha256:b76a4de66a0ac01da1be14ecc8ae88ddea33b8380284a9e3eae39d57ebcbe26e", size = 2352212, upload-time = "2025-09-11T22:59:26.576Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "1.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
@@ -477,6 +539,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/18/a8444036c6dd65ba3624c63b734d3ba95ba63ace513078e1580590075d21/pastel-0.2.1-py2.py3-none-any.whl", hash = "sha256:4349225fcdf6c2bb34d483e523475de5bb04a5c10ef711263452cb37d7dd4364", size = 5955, upload-time = "2020-09-16T19:21:11.409Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "4.3.7"
|
||||
@@ -518,91 +589,135 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.10.6"
|
||||
version = "2.12.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.27.2"
|
||||
version = "2.41.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/bc/fed5f74b5d802cf9a03e83f60f18864e90e3aed7223adaca5ffb7a8d8d64/pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa", size = 1895938, upload-time = "2024-12-18T11:27:14.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/2a/185aff24ce844e39abb8dd680f4e959f0006944f4a8a0ea372d9f9ae2e53/pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c", size = 1815684, upload-time = "2024-12-18T11:27:16.489Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/43/fafabd3d94d159d4f1ed62e383e264f146a17dd4d48453319fd782e7979e/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7969e133a6f183be60e9f6f56bfae753585680f3b7307a8e555a948d443cc05a", size = 1829169, upload-time = "2024-12-18T11:27:22.16Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/d1/f2dfe1a2a637ce6800b799aa086d079998959f6f1215eb4497966efd2274/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3de9961f2a346257caf0aa508a4da705467f53778e9ef6fe744c038119737ef5", size = 1867227, upload-time = "2024-12-18T11:27:25.097Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/39/e06fcbcc1c785daa3160ccf6c1c38fea31f5754b756e34b65f74e99780b5/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2bb4d3e5873c37bb3dd58714d4cd0b0e6238cebc4177ac8fe878f8b3aa8e74c", size = 2037695, upload-time = "2024-12-18T11:27:28.656Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/67/61291ee98e07f0650eb756d44998214231f50751ba7e13f4f325d95249ab/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:280d219beebb0752699480fe8f1dc61ab6615c2046d76b7ab7ee38858de0a4e7", size = 2741662, upload-time = "2024-12-18T11:27:30.798Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/90/3b15e31b88ca39e9e626630b4c4a1f5a0dfd09076366f4219429e6786076/pydantic_core-2.27.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47956ae78b6422cbd46f772f1746799cbb862de838fd8d1fbd34a82e05b0983a", size = 1993370, upload-time = "2024-12-18T11:27:33.692Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/83/c06d333ee3a67e2e13e07794995c1535565132940715931c1c43bfc85b11/pydantic_core-2.27.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:14d4a5c49d2f009d62a2a7140d3064f686d17a5d1a268bc641954ba181880236", size = 1996813, upload-time = "2024-12-18T11:27:37.111Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/f7/89be1c8deb6e22618a74f0ca0d933fdcb8baa254753b26b25ad3acff8f74/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:337b443af21d488716f8d0b6164de833e788aa6bd7e3a39c005febc1284f4962", size = 2005287, upload-time = "2024-12-18T11:27:40.566Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/7d/8eb3e23206c00ef7feee17b83a4ffa0a623eb1a9d382e56e4aa46fd15ff2/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:03d0f86ea3184a12f41a2d23f7ccb79cdb5a18e06993f8a45baa8dfec746f0e9", size = 2128414, upload-time = "2024-12-18T11:27:43.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/99/fe80f3ff8dd71a3ea15763878d464476e6cb0a2db95ff1c5c554133b6b83/pydantic_core-2.27.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7041c36f5680c6e0f08d922aed302e98b3745d97fe1589db0a3eebf6624523af", size = 2155301, upload-time = "2024-12-18T11:27:47.36Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/a3/e50460b9a5789ca1451b70d4f52546fa9e2b420ba3bfa6100105c0559238/pydantic_core-2.27.2-cp310-cp310-win32.whl", hash = "sha256:50a68f3e3819077be2c98110c1f9dcb3817e93f267ba80a2c05bb4f8799e2ff4", size = 1816685, upload-time = "2024-12-18T11:27:50.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/4c/a8838731cb0f2c2a39d3535376466de6049034d7b239c0202a64aaa05533/pydantic_core-2.27.2-cp310-cp310-win_amd64.whl", hash = "sha256:e0fd26b16394ead34a424eecf8a31a1f5137094cabe84a1bcb10fa6ba39d3d31", size = 1982876, upload-time = "2024-12-18T11:27:53.54Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/72/af70981a341500419e67d5cb45abe552a7c74b66326ac8877588488da1ac/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:2bf14caea37e91198329b828eae1618c068dfb8ef17bb33287a7ad4b61ac314e", size = 1891159, upload-time = "2024-12-18T11:30:54.382Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/3d/c5913cccdef93e0a6a95c2d057d2c2cba347815c845cda79ddd3c0f5e17d/pydantic_core-2.27.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:b0cb791f5b45307caae8810c2023a184c74605ec3bcbb67d13846c28ff731ff8", size = 1768331, upload-time = "2024-12-18T11:30:58.178Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/f0/a3ae8fbee269e4934f14e2e0e00928f9346c5943174f2811193113e58252/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:688d3fd9fcb71f41c4c015c023d12a79d1c4c0732ec9eb35d96e3388a120dcf3", size = 1822467, upload-time = "2024-12-18T11:31:00.6Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/7a/7bbf241a04e9f9ea24cd5874354a83526d639b02674648af3f350554276c/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d591580c34f4d731592f0e9fe40f9cc1b430d297eecc70b962e93c5c668f15f", size = 1979797, upload-time = "2024-12-18T11:31:07.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4f/5f/4784c6107731f89e0005a92ecb8a2efeafdb55eb992b8e9d0a2be5199335/pydantic_core-2.27.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:82f986faf4e644ffc189a7f1aafc86e46ef70372bb153e7001e8afccc6e54133", size = 1987839, upload-time = "2024-12-18T11:31:09.775Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/a7/61246562b651dff00de86a5f01b6e4befb518df314c54dec187a78d81c84/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:bec317a27290e2537f922639cafd54990551725fc844249e64c523301d0822fc", size = 1998861, upload-time = "2024-12-18T11:31:13.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/aa/837821ecf0c022bbb74ca132e117c358321e72e7f9702d1b6a03758545e2/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:0296abcb83a797db256b773f45773da397da75a08f5fcaef41f2044adec05f50", size = 2116582, upload-time = "2024-12-18T11:31:17.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/b0/5e74656e95623cbaa0a6278d16cf15e10a51f6002e3ec126541e95c29ea3/pydantic_core-2.27.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:0d75070718e369e452075a6017fbf187f788e17ed67a3abd47fa934d001863d9", size = 2151985, upload-time = "2024-12-18T11:31:19.901Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/63/37/3e32eeb2a451fddaa3898e2163746b0cffbbdbb4740d38372db0490d67f3/pydantic_core-2.27.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:7e17b560be3c98a8e3aa66ce828bdebb9e9ac6ad5466fba92eb74c4c95cb1151", size = 2004715, upload-time = "2024-12-18T11:31:22.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/90/32c9941e728d564b411d574d8ee0cf09b12ec978cb22b294995bae5549a5/pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146", size = 2107298, upload-time = "2025-11-04T13:39:04.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a8/61c96a77fe28993d9a6fb0f4127e05430a267b235a124545d79fea46dd65/pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2", size = 1901475, upload-time = "2025-11-04T13:39:06.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/b6/338abf60225acc18cdc08b4faef592d0310923d19a87fba1faf05af5346e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97", size = 1918815, upload-time = "2025-11-04T13:39:10.41Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/1c/2ed0433e682983d8e8cba9c8d8ef274d4791ec6a6f24c58935b90e780e0a/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9", size = 2065567, upload-time = "2025-11-04T13:39:12.244Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/24/cf84974ee7d6eae06b9e63289b7b8f6549d416b5c199ca2d7ce13bbcf619/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52", size = 2230442, upload-time = "2025-11-04T13:39:13.962Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/21/4e287865504b3edc0136c89c9c09431be326168b1eb7841911cbc877a995/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941", size = 2350956, upload-time = "2025-11-04T13:39:15.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/76/7727ef2ffa4b62fcab916686a68a0426b9b790139720e1934e8ba797e238/pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a", size = 2068253, upload-time = "2025-11-04T13:39:17.403Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/8c/a4abfc79604bcb4c748e18975c44f94f756f08fb04218d5cb87eb0d3a63e/pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c", size = 2177050, upload-time = "2025-11-04T13:39:19.351Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/b1/de2e9a9a79b480f9cb0b6e8b6ba4c50b18d4e89852426364c66aa82bb7b3/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2", size = 2147178, upload-time = "2025-11-04T13:39:21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/16/c1/dfb33f837a47b20417500efaa0378adc6635b3c79e8369ff7a03c494b4ac/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556", size = 2341833, upload-time = "2025-11-04T13:39:22.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/36/00f398642a0f4b815a9a558c4f1dca1b4020a7d49562807d7bc9ff279a6c/pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49", size = 2321156, upload-time = "2025-11-04T13:39:25.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/70/cad3acd89fde2010807354d978725ae111ddf6d0ea46d1ea1775b5c1bd0c/pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba", size = 1989378, upload-time = "2025-11-04T13:39:27.92Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/92/d338652464c6c367e5608e4488201702cd1cbb0f33f7b6a85a60fe5f3720/pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9", size = 2013622, upload-time = "2025-11-04T13:39:29.848Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/b0/1a2aa41e3b5a4ba11420aba2d091b2d17959c8d1519ece3627c371951e73/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8", size = 2103351, upload-time = "2025-11-04T13:43:02.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/ee/31b1f0020baaf6d091c87900ae05c6aeae101fa4e188e1613c80e4f1ea31/pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a", size = 1925363, upload-time = "2025-11-04T13:43:05.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/89/ab8e86208467e467a80deaca4e434adac37b10a9d134cd2f99b28a01e483/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b", size = 2135615, upload-time = "2025-11-04T13:43:08.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/0a/99a53d06dd0348b2008f2f30884b34719c323f16c3be4e6cc1203b74a91d/pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2", size = 2175369, upload-time = "2025-11-04T13:43:12.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/94/30ca3b73c6d485b9bb0bc66e611cff4a7138ff9736b7e66bcf0852151636/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093", size = 2144218, upload-time = "2025-11-04T13:43:15.431Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/57/31b4f8e12680b739a91f472b5671294236b82586889ef764b5fbc6669238/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a", size = 2329951, upload-time = "2025-11-04T13:43:18.062Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/73/3c2c8edef77b8f7310e6fb012dbc4b8551386ed575b9eb6fb2506e28a7eb/pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963", size = 2318428, upload-time = "2025-11-04T13:43:20.679Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2f/02/8559b1f26ee0d502c74f9cca5c0d2fd97e967e083e006bbbb4e97f3a043a/pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a", size = 2147009, upload-time = "2025-11-04T13:43:23.286Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -901,20 +1016,72 @@ wheels = [
|
||||
name = "sphinx-autobuild"
|
||||
version = "2024.10.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama" },
|
||||
{ name = "colorama", marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn" },
|
||||
{ name = "watchfiles" },
|
||||
{ name = "websockets" },
|
||||
{ name = "starlette", marker = "python_full_version < '3.11'" },
|
||||
{ name = "uvicorn", marker = "python_full_version < '3.11'" },
|
||||
{ name = "watchfiles", marker = "python_full_version < '3.11'" },
|
||||
{ name = "websockets", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a5/2c/155e1de2c1ba96a72e5dba152c509a8b41e047ee5c2def9e9f0d812f8be7/sphinx_autobuild-2024.10.3.tar.gz", hash = "sha256:248150f8f333e825107b6d4b86113ab28fa51750e5f9ae63b59dc339be951fb1", size = 14023, upload-time = "2024-10-02T23:15:30.172Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/c0/eba125db38c84d3c74717008fd3cb5000b68cd7e2cbafd1349c6a38c3d3b/sphinx_autobuild-2024.10.3-py3-none-any.whl", hash = "sha256:158e16c36f9d633e613c9aaf81c19b0fc458ca78b112533b20dafcda430d60fa", size = 11908, upload-time = "2024-10-02T23:15:28.739Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-autobuild"
|
||||
version = "2025.8.25"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "starlette", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "uvicorn", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "watchfiles", marker = "python_full_version >= '3.11'" },
|
||||
{ name = "websockets", marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e0/3c/a59a3a453d4133777f7ed2e83c80b7dc817d43c74b74298ca0af869662ad/sphinx_autobuild-2025.8.25.tar.gz", hash = "sha256:9cf5aab32853c8c31af572e4fecdc09c997e2b8be5a07daf2a389e270e85b213", size = 15200, upload-time = "2025-08-25T18:44:55.436Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d7/20/56411b52f917696995f5ad27d2ea7e9492c84a043c5b49a3a3173573cd93/sphinx_autobuild-2025.8.25-py3-none-any.whl", hash = "sha256:b750ac7d5a18603e4665294323fd20f6dcc0a984117026d1986704fa68f0379a", size = 12535, upload-time = "2025-08-25T18:44:54.164Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-autodoc-typehints"
|
||||
version = "3.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "sphinx", version = "8.1.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/26/f0/43c6a5ff3e7b08a8c3b32f81b859f1b518ccc31e45f22e2b41ced38be7b9/sphinx_autodoc_typehints-3.0.1.tar.gz", hash = "sha256:b9b40dd15dee54f6f810c924f863f9cf1c54f9f3265c495140ea01be7f44fa55", size = 36282, upload-time = "2025-01-16T18:25:30.958Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/dc/dc46c5c7c566b7ec5e8f860f9c89533bf03c0e6aadc96fb9b337867e4460/sphinx_autodoc_typehints-3.0.1-py3-none-any.whl", hash = "sha256:4b64b676a14b5b79cefb6628a6dc8070e320d4963e8ff640a2f3e9390ae9045a", size = 20245, upload-time = "2025-01-16T18:25:27.394Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-autodoc-typehints"
|
||||
version = "3.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.11'",
|
||||
]
|
||||
dependencies = [
|
||||
{ name = "sphinx", version = "8.2.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/93/68/a388a9b8f066cd865d9daa65af589d097efbfab9a8c302d2cb2daa43b52e/sphinx_autodoc_typehints-3.2.0.tar.gz", hash = "sha256:107ac98bc8b4837202c88c0736d59d6da44076e65a0d7d7d543a78631f662a9b", size = 36724, upload-time = "2025-04-25T16:53:25.872Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/f7/c7/8aab362e86cbf887e58be749a78d20ad743e1eb2c73c2b13d4761f39a104/sphinx_autodoc_typehints-3.2.0-py3-none-any.whl", hash = "sha256:884b39be23b1d884dcc825d4680c9c6357a476936e3b381a67ae80091984eb49", size = 20563, upload-time = "2025-04-25T16:53:24.492Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sphinx-rtd-theme"
|
||||
version = "3.0.2"
|
||||
@@ -1011,50 +1178,84 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "tomli"
|
||||
version = "2.2.1"
|
||||
version = "2.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175, upload-time = "2024-11-27T22:38:36.873Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077, upload-time = "2024-11-27T22:37:54.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429, upload-time = "2024-11-27T22:37:56.698Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067, upload-time = "2024-11-27T22:37:57.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030, upload-time = "2024-11-27T22:37:59.344Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898, upload-time = "2024-11-27T22:38:00.429Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894, upload-time = "2024-11-27T22:38:02.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319, upload-time = "2024-11-27T22:38:03.206Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273, upload-time = "2024-11-27T22:38:04.217Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310, upload-time = "2024-11-27T22:38:05.908Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309, upload-time = "2024-11-27T22:38:06.812Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762, upload-time = "2024-11-27T22:38:07.731Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453, upload-time = "2024-11-27T22:38:09.384Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486, upload-time = "2024-11-27T22:38:10.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349, upload-time = "2024-11-27T22:38:11.443Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159, upload-time = "2024-11-27T22:38:13.099Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243, upload-time = "2024-11-27T22:38:14.766Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645, upload-time = "2024-11-27T22:38:15.843Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584, upload-time = "2024-11-27T22:38:17.645Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875, upload-time = "2024-11-27T22:38:19.159Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418, upload-time = "2024-11-27T22:38:20.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708, upload-time = "2024-11-27T22:38:21.659Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582, upload-time = "2024-11-27T22:38:22.693Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543, upload-time = "2024-11-27T22:38:24.367Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691, upload-time = "2024-11-27T22:38:26.081Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170, upload-time = "2024-11-27T22:38:27.921Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530, upload-time = "2024-11-27T22:38:29.591Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666, upload-time = "2024-11-27T22:38:30.639Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954, upload-time = "2024-11-27T22:38:31.702Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724, upload-time = "2024-11-27T22:38:32.837Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383, upload-time = "2024-11-27T22:38:34.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257, upload-time = "2024-11-27T22:38:35.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "types-jsonschema"
|
||||
version = "4.25.1.20250822"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "referencing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/64/7f/369b54dad6eb6b5adc1fb1c53edbed18e6c32cbc600357135308902fdbdc/types_jsonschema-4.25.1.20250822.tar.gz", hash = "sha256:aac69ed4b23f49aaceb7fcb834141d61b9e4e6a7f6008cb2f0d3b831dfa8464a", size = 15628, upload-time = "2025-08-22T03:04:18.293Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/3d/bc1d171f032fcf63cedd4ade241f3f4e66d7e3bb53ee1da3c8f2f043eb0b/types_jsonschema-4.25.1.20250822-py3-none-any.whl", hash = "sha256:f82c2d7fa1ce1c0b84ba1de4ed6798469768188884db04e66421913a4e181294", size = 15923, upload-time = "2025-08-22T03:04:17.346Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
version = "4.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321, upload-time = "2024-06-07T18:52:15.995Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438, upload-time = "2024-06-07T18:52:13.582Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user