Here the classes needed to implement custom mappings are imported.
This section can just be copied without changes.
The ID_MAPPING "constant" definition / assignment.
-
The section defining the mapping Dtos. More on this later.
-
The DTO_MAPPING "constant" definition / assignment.
+
The section defining the RelationsMappings. More on this later.
+
The RELATIONS_MAPPING "constant" definition / assignment.
The PATH_MAPPING "constant" definition / assignment.
-
The ID_MAPPING, DTO_MAPPING and PATH_MAPPING
-When a custom mappings file is used, the OpenApiLibCore will attempt to import it and then import DTO_MAPPING, PATH_MAPPING and ID_MAPPING from it.
+
The ID_MAPPING, RELATIONS_MAPPING and PATH_MAPPING
+When a custom mappings file is used, the OpenApiLibCore will attempt to import it and then import RELATIONS_MAPPING, PATH_MAPPING and ID_MAPPING from it.
For this reason, the exact same name must be used in a custom mappings file (capitilization matters).
The ID_MAPPING
@@ -721,18 +721,18 @@
The ID_MAPPING
-
The DTO_MAPPING
-The DTO_MAPPING is a dictionary with a tuple as its key and a mappings Dto as its value.
+
The RELATIONS_MAPPING
+The RELATIONS_MAPPING is a dictionary with a tuple as its key and a RelationsMapping as its value.
The tuple must be in the form ("path_from_the_paths_section", "method_supported_by_the_path").
The path_from_the_paths_section must be exactly as found in the openapi document.
The method_supported_by_the_path must be one of the methods supported by the path and must be in lowercase.
The PATH_MAPPING
-The PATH_MAPPING is a dictionary with a "path_from_the_paths_section" as its key and a mappings Dto as its value.
+The PATH_MAPPING is a dictionary with a "path_from_the_paths_section" as its key and a RelationsMapping as its value.
The path_from_the_paths_section must be exactly as found in the openapi document.
-
Dto mapping classes
+
RelationsMapping classes
As can be seen from the import section above, a number of classes are available to deal with relations between resources and / or constraints on properties.
Each of these classes is designed to handle a relation or constraint commonly seen in REST APIs.
@@ -757,7 +757,7 @@
To verify that the specified error_code occurs when attempting to post an Employee with an employee_number that is already in use, we can implement the following dependency:
-Note how this example reuses the EmployeeDto to model the uniqueness constraint for all the operations (post, put and patch) that all relate to the same employee_number.
+Note how this example reuses the EmployeeMapping to model the uniqueness constraint for all the operations (post, put and patch) that all relate to the same employee_number.
@@ -867,7 +867,7 @@
PropertyValueConstraint
This type of constraint can be modeled as follows:
To support additional restrictions like these, the PropertyValueConstraint supports two additional properties: error_value and invalid_value_error_code:
-Note: The PathPropertiesConstraint is only applicable to the get_path_relations in a Dto and only the PATH_MAPPING uses the get_path_relations.
+Note: The PathPropertiesConstraint is only applicable to the get_path_relations in a RelationsMapping and only the PATH_MAPPING uses the get_path_relations.
To be able to automatically perform endpoint validations, the OpenApiLibCore has to construct the url for the resource from the path as found in the openapi document.
@@ -970,7 +970,7 @@
PathPropertiesConstraint
It should be clear that the OpenApiLibCore won't be able to acquire a valid month and date. The PathPropertiesConstraint can be used in this case:
To prevent OpenApiLibCore from generating invalid combinations of path and query parameters in this type of endpoint, the IGNORE special value can be used to ensure the related query parameter is never send in a request.
Verify that you have JavaScript enabled in your browser.
-
- Make sure you are using a modern enough browser. If using
- Internet Explorer, version 11 is required.
-
-
- Check are there messages in your browser's
- JavaScript error log. Please report the problem if you suspect
- you have encountered a bug.
-
-
-
+
+
Opening library documentation failed
+
+
Verify that you have JavaScript enabled in your browser.
+
+Make sure you are using a modern enough browser. If using
+Internet Explorer, version 11 is required.
+
+
+Check are there messages in your browser's
+JavaScript error log. Please report the problem if you suspect
+you have encountered a bug.
+
+
+
-
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
-
-
+ data-v-2754030d="" fill="var(--text-color)">`,t.classList.add("modal-close-button");let r=document.createElement("div");r.classList.add("modal-close-button-container"),r.appendChild(t),t.addEventListener("click",()=>{rl()}),e.appendChild(r),r.addEventListener("click",()=>{rl()});let n=document.createElement("div");n.id="modal",n.classList.add("modal"),n.addEventListener("click",({target:e})=>{"A"===e.tagName.toUpperCase()&&rl()});let o=document.createElement("div");o.id="modal-content",o.classList.add("modal-content"),n.appendChild(o),e.appendChild(n),document.body.appendChild(e),document.addEventListener("keydown",({key:e})=>{"Escape"===e&&rl()})}()}renderTemplates(){this.renderLibdocTemplate("base",this.libdoc,"#root"),this.libdoc.inits.length>0&&this.renderImporting(),this.renderShortcuts(),this.renderKeywords(),this.renderLibdocTemplate("data-types"),this.renderLibdocTemplate("footer")}initHashEvents(){window.addEventListener("hashchange",function(){document.getElementsByClassName("hamburger-menu")[0].checked=!1},!1),window.addEventListener("hashchange",function(){if(0==window.location.hash.indexOf("#type-")){let e="#type-modal-"+decodeURI(window.location.hash.slice(6)),t=document.querySelector(".data-types").querySelector(e);t&&rs(t)}},!1),this.scrollToHash()}initTagSearch(){let e=new URLSearchParams(window.location.search),t="";e.has("tag")&&(t=e.get("tag"),this.tagSearch(t,window.location.hash)),this.libdoc.tags.length&&(this.libdoc.selectedTag=t,this.renderLibdocTemplate("tags-shortcuts"),document.getElementById("tags-shortcuts-container").onchange=e=>{let t=e.target.selectedOptions[0].value;""!=t?this.tagSearch(t):this.clearTagSearch()})}initLanguageMenu(){this.renderTemplate("language",{languages:this.translations.getLanguageCodes()}),document.querySelectorAll("#language-container ul a").forEach(e=>{e.innerHTML===this.translations.currentLanguage()&&e.classList.toggle("selected"),e.addEventListener("click",()=>{this.translations.setLanguage(e.innerHTML)&&this.render()})}),document.querySelector("#language-container button").addEventListener("click",()=>{document.querySelector("#language-container ul").classList.toggle("hidden")})}renderImporting(){this.renderLibdocTemplate("importing"),this.registerTypeDocHandlers("#importing-container")}renderShortcuts(){this.renderLibdocTemplate("shortcuts"),document.getElementById("toggle-keyword-shortcuts").addEventListener("click",()=>this.toggleShortcuts()),document.querySelector(".clear-search").addEventListener("click",()=>this.clearSearch()),document.querySelector(".search-input").addEventListener("keydown",()=>rc(()=>this.searching(),150)),this.renderLibdocTemplate("keyword-shortcuts"),document.querySelectorAll("a.match").forEach(e=>e.addEventListener("click",this.closeMenu))}registerTypeDocHandlers(e){document.querySelectorAll(`${e} a.type`).forEach(e=>e.addEventListener("click",e=>{let t=e.target.dataset.typedoc;rs(document.querySelector(`#type-modal-${t}`))}))}renderKeywords(e=null){null==e&&(e=this.libdoc),this.renderLibdocTemplate("keywords",e),document.querySelectorAll(".kw-tags span").forEach(e=>{e.addEventListener("click",e=>{this.tagSearch(e.target.innerText)})}),this.registerTypeDocHandlers("#keywords-container"),document.getElementById("keyword-statistics-header").innerText=""+this.libdoc.keywords.length}setTheme(){document.documentElement.setAttribute("data-theme",this.getTheme())}getTheme(){return null!=this.libdoc.theme?this.libdoc.theme:window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}scrollToHash(){if(window.location.hash){let e=window.location.hash.substring(1),t=document.getElementById(decodeURIComponent(e));null!=t&&t.scrollIntoView()}}tagSearch(e,t){document.getElementsByClassName("search-input")[0].value="";let r={tags:!0,tagsExact:!0},n=window.location.pathname+"?tag="+e+(t||"");this.markMatches(e,r),this.highlightMatches(e,r),history.replaceState&&history.replaceState(null,"",n),document.getElementById("keyword-shortcuts-container").scrollTop=0}clearTagSearch(){document.getElementsByClassName("search-input")[0].value="",history.replaceState&&history.replaceState(null,"",window.location.pathname),this.resetKeywords()}searching(){this.searchTime=Date.now();let e=document.getElementsByClassName("search-input")[0].value,t={name:!0,args:!0,doc:!0,tags:!0};e?requestAnimationFrame(()=>{this.markMatches(e,t,this.searchTime,()=>{this.highlightMatches(e,t,this.searchTime),document.getElementById("keyword-shortcuts-container").scrollTop=0})}):this.resetKeywords()}highlightMatches(e,t,n){if(n&&n!==this.searchTime)return;let o=document.querySelectorAll("#shortcuts-container .match"),i=document.querySelectorAll("#keywords-container .match");if(t.name&&(new(r(ef))(o).mark(e),new(r(ef))(i).mark(e)),t.args&&new(r(ef))(document.querySelectorAll("#keywords-container .match .args")).mark(e),t.doc&&new(r(ef))(document.querySelectorAll("#keywords-container .match .doc")).mark(e),t.tags){let n=document.querySelectorAll("#keywords-container .match .tags a, #tags-shortcuts-container .match .tags a");if(t.tagsExact){let t=[];n.forEach(r=>{r.textContent?.toUpperCase()==e.toUpperCase()&&t.push(r)}),new(r(ef))(t).mark(e)}else new(r(ef))(n).mark(e)}}markMatches(e,t,r,n){if(r&&r!==this.searchTime)return;let o=e.replace(/[-[\]{}()+?*.,\\^$|#]/g,"\\$&");t.tagsExact&&(o="^"+o+"$");let i=RegExp(o,"i"),a=i.test.bind(i),s={},l=0;s.keywords=this.libdoc.keywords.map(e=>{let r={...e};return r.hidden=!(t.name&&a(r.name))&&!(t.args&&a(r.args))&&!(t.doc&&a(r.doc))&&!(t.tags&&r.tags.some(a)),!r.hidden&&l++,r}),this.renderLibdocTemplate("keyword-shortcuts",s),this.renderKeywords(s),this.libdoc.tags.length&&(this.libdoc.selectedTag=t.tagsExact?e:"",this.renderLibdocTemplate("tags-shortcuts")),document.getElementById("keyword-statistics-header").innerText=l+" / "+s.keywords.length,0===l&&(document.querySelector("#keywords-container table").innerHTML=""),n&&requestAnimationFrame(n)}closeMenu(){document.getElementById("hamburger-menu-input").checked=!1}openKeywordWall(){document.getElementsByClassName("shortcuts")[0].classList.add("keyword-wall"),this.storage.set("keyword-wall","open"),document.getElementById("toggle-keyword-shortcuts").innerText="-"}closeKeywordWall(){document.getElementsByClassName("shortcuts")[0].classList.remove("keyword-wall"),this.storage.set("keyword-wall","close"),document.getElementById("toggle-keyword-shortcuts").innerText="+"}toggleShortcuts(){document.getElementsByClassName("shortcuts")[0].classList.contains("keyword-wall")?this.closeKeywordWall():this.openKeywordWall()}resetKeywords(){this.renderLibdocTemplate("keyword-shortcuts"),this.renderKeywords(),this.libdoc.tags.length&&(this.libdoc.selectedTag="",this.renderLibdocTemplate("tags-shortcuts")),history.replaceState&&history.replaceState(null,"",location.pathname)}clearSearch(){document.getElementsByClassName("search-input")[0].value="";let e=document.getElementById("tags-shortcuts-container");e&&(e.selectedIndex=0),this.resetKeywords()}renderLibdocTemplate(e,t=null,r=""){null==t&&(t=this.libdoc),this.renderTemplate(e,t,r)}renderTemplate(e,t,n=""){let o=document.getElementById(`${e}-template`)?.innerHTML,i=r(eg).compile(o);""===n&&(n=`#${e}-container`),document.body.querySelector(n).innerHTML=i(t)}},rh=libdoc;const rp=new eh("libdoc"),rd=ed.getInstance(rh.lang);new ru(rh,rp,rd).render();
+
+
+
diff --git a/docs/releases.md b/docs/releases.md
index fc45a8a..77136e4 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1,14 +1,60 @@
# Release notes
-## OpenApiTools v1.0.5
+## OpenApiTools v2.0.0
+
+### Major changes and new features
+- Request bodies now support all JSON types, not just `objects` (`dicts`).
+ - This closes [issue #9: No body generated when root is a list](https://github.com/MarketSquare/robotframework-openapitools/issues/9).
+ - The `Relations` still need to be reworked to align with this change.
+- Refactored retrieving / loading of the OpenAPI spec.
+ - This closes [issue #93: SSL error even if cert / verify is set](https://github.com/MarketSquare/robotframework-openapitools/issues/93).
+- Added keywords to make it easier to work with the `RequestValues` object:
+ - `Get Request Values Object` can be used to create a `RequestValues` instance from pre-defined values (where `Get Request Values` generates all values automatically).
+ - `Perform Authorized Request` is functionally the same as exisiting `Authorized Request` keyword, but it accepts a `RequestValues` instance as argument.
+ - `Validated Request` is functionally the same as the existing `Perform Validated Request` keyword, but it accepts the data as separate arguments instead of the `RequestValues`.
+ - `Convert Request Values To Dict` can be used to get a (Python) dict represenation of a `RequestValues` object that can be used with e.g. the Collections keywords for working with dictionaries.
+ - Thise closes [issue #98: Add keywords to simplify using Authorized Request and Perform Validated Request](https://github.com/MarketSquare/robotframework-openapitools/issues/98).
+- Improved handling of `treat_as_mandatory` on a `PropertyValueConstraint`.
+- Added support for using `IGNORE` as `invalid_value` on a `PropertyValueConstraint`.
### Bugfixes
-- `parameters` at path level are not taken into account at operation level
+- Added support for the `nullable` property in OAS 3.0 schemas when generating data.
+ - This closes [issue #81: nullable not taken into account in get_valid_value](https://github.com/MarketSquare/robotframework-openapitools/issues/81).
+- Support added for multiple instances of OpenApiLibCore within the same suite.
+ - This closes [issue #96: Multiple keywords with same name error when using multiple generated libraries](https://github.com/MarketSquare/robotframework-openapitools/issues/96).
+- Fixed validation errors caused by `Content-Type` not being handled case-insensitive.
+- Fixed an exception during validation caused by `charset` being included in the `Content-Type` header for `application/json`.
+
+### Breaking changes
+- Addressing [issue #95: Refactor: better name for Dto](https://github.com/MarketSquare/robotframework-openapitools/issues/95) introduces a number breaking renames:
+ - `Dto` has been renamed to `RelationsMapping`.
+ - `constraint_mapping` has been renamed to `relations_mapping` in a number of places.
+ - `DTO_MAPPING` has been renamed to `RELATIONS_MAPPING`.
+- The `RequestData` class that is returned by a number of keywords has been changed:
+ - The `dto` property was removed.
+ - The `valid_data` property was added.
+ - The `relations_mapping` property was added.
+- `invalid_property_default_response` library parameter renamed to `invalid_data_default_response`.
+
+### Additional changes
+- Special handling of `"format": "byte"` for `"type": "string"` (OAS 3.0) was removed.
+ - While some logic related to this worked, the result was never JSON-serializable.
+- The devcontainer setup was updated.
+- The GitHub pipeline was updated to include Python 3.14.
+- Updated minimum version markers for many dependencies.
+- Annotations are now complete (as far as possible under Python 3.10).
## Previous versions
+### OpenApiTools v1.0.5
+
+#### Bugfixes
+- `parameters` at path level are not taken into account at operation level.
+
+---
+
### OpenApiTools v1.0.4
#### Bugfixes
@@ -41,7 +87,7 @@
### OpenApiTools v1.0.1
#### Bugfixes
-- `openapitools_docs` was missing from package distribution
+- `openapitools_docs` was missing from package distribution.
---
diff --git a/poetry.lock b/poetry.lock
index 7c67acb..af9639b 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,15 +1,15 @@
-# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand.
[[package]]
name = "annotated-doc"
-version = "0.0.3"
+version = "0.0.4"
description = "Document parameters, class attributes, return types, and variables inline, with Annotated."
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
- {file = "annotated_doc-0.0.3-py3-none-any.whl", hash = "sha256:348ec6664a76f1fd3be81f43dffbee4c7e8ce931ba71ec67cc7f4ade7fbbb580"},
- {file = "annotated_doc-0.0.3.tar.gz", hash = "sha256:e18370014c70187422c33e945053ff4c286f453a984eba84d0dbfa0c935adeda"},
+ {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"},
+ {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"},
]
[[package]]
@@ -26,35 +26,34 @@ files = [
[[package]]
name = "anyio"
-version = "4.11.0"
+version = "4.12.0"
description = "High-level concurrency and networking framework on top of asyncio or Trio"
optional = false
python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"},
- {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"},
+ {file = "anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb"},
+ {file = "anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0"},
]
[package.dependencies]
exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""}
idna = ">=2.8"
-sniffio = ">=1.1"
typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
-trio = ["trio (>=0.31.0)"]
+trio = ["trio (>=0.31.0) ; python_version < \"3.10\"", "trio (>=0.32.0) ; python_version >= \"3.10\""]
[[package]]
name = "astroid"
-version = "4.0.1"
+version = "4.0.2"
description = "An abstract syntax tree for Python with inference support."
optional = false
python-versions = ">=3.10.0"
groups = ["lint-and-format"]
files = [
- {file = "astroid-4.0.1-py3-none-any.whl", hash = "sha256:37ab2f107d14dc173412327febf6c78d39590fdafcb44868f03b6c03452e3db0"},
- {file = "astroid-4.0.1.tar.gz", hash = "sha256:0d778ec0def05b935e198412e62f9bcca8b3b5c39fdbe50b0ba074005e477aab"},
+ {file = "astroid-4.0.2-py3-none-any.whl", hash = "sha256:d7546c00a12efc32650b19a2bb66a153883185d3179ab0d4868086f807338b9b"},
+ {file = "astroid-4.0.2.tar.gz", hash = "sha256:ac8fb7ca1c08eb9afec91ccc23edbd8ac73bb22cbdd7da1d488d9fb8d6579070"},
]
[package.dependencies]
@@ -72,64 +71,16 @@ files = [
{file = "attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11"},
]
-[[package]]
-name = "black"
-version = "25.9.0"
-description = "The uncompromising code formatter."
-optional = false
-python-versions = ">=3.9"
-groups = ["main"]
-files = [
- {file = "black-25.9.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ce41ed2614b706fd55fd0b4a6909d06b5bab344ffbfadc6ef34ae50adba3d4f7"},
- {file = "black-25.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2ab0ce111ef026790e9b13bd216fa7bc48edd934ffc4cbf78808b235793cbc92"},
- {file = "black-25.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f96b6726d690c96c60ba682955199f8c39abc1ae0c3a494a9c62c0184049a713"},
- {file = "black-25.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:d119957b37cc641596063cd7db2656c5be3752ac17877017b2ffcdb9dfc4d2b1"},
- {file = "black-25.9.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:456386fe87bad41b806d53c062e2974615825c7a52159cde7ccaeb0695fa28fa"},
- {file = "black-25.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a16b14a44c1af60a210d8da28e108e13e75a284bf21a9afa6b4571f96ab8bb9d"},
- {file = "black-25.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aaf319612536d502fdd0e88ce52d8f1352b2c0a955cc2798f79eeca9d3af0608"},
- {file = "black-25.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:c0372a93e16b3954208417bfe448e09b0de5cc721d521866cd9e0acac3c04a1f"},
- {file = "black-25.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1b9dc70c21ef8b43248f1d86aedd2aaf75ae110b958a7909ad8463c4aa0880b0"},
- {file = "black-25.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8e46eecf65a095fa62e53245ae2795c90bdecabd53b50c448d0a8bcd0d2e74c4"},
- {file = "black-25.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9101ee58ddc2442199a25cb648d46ba22cd580b00ca4b44234a324e3ec7a0f7e"},
- {file = "black-25.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:77e7060a00c5ec4b3367c55f39cf9b06e68965a4f2e61cecacd6d0d9b7ec945a"},
- {file = "black-25.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0172a012f725b792c358d57fe7b6b6e8e67375dd157f64fa7a3097b3ed3e2175"},
- {file = "black-25.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3bec74ee60f8dfef564b573a96b8930f7b6a538e846123d5ad77ba14a8d7a64f"},
- {file = "black-25.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b756fc75871cb1bcac5499552d771822fd9db5a2bb8db2a7247936ca48f39831"},
- {file = "black-25.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:846d58e3ce7879ec1ffe816bb9df6d006cd9590515ed5d17db14e17666b2b357"},
- {file = "black-25.9.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ef69351df3c84485a8beb6f7b8f9721e2009e20ef80a8d619e2d1788b7816d47"},
- {file = "black-25.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e3c1f4cd5e93842774d9ee4ef6cd8d17790e65f44f7cdbaab5f2cf8ccf22a823"},
- {file = "black-25.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:154b06d618233fe468236ba1f0e40823d4eb08b26f5e9261526fde34916b9140"},
- {file = "black-25.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e593466de7b998374ea2585a471ba90553283fb9beefcfa430d84a2651ed5933"},
- {file = "black-25.9.0-py3-none-any.whl", hash = "sha256:474b34c1342cdc157d307b56c4c65bce916480c4a8f6551fdc6bf9b486a7c4ae"},
- {file = "black-25.9.0.tar.gz", hash = "sha256:0474bca9a0dd1b51791fcc507a4e02078a1c63f6d4e4ae5544b9848c7adfb619"},
-]
-
-[package.dependencies]
-click = ">=8.0.0"
-mypy-extensions = ">=0.4.3"
-packaging = ">=22.0"
-pathspec = ">=0.9.0"
-platformdirs = ">=2"
-pytokens = ">=0.1.10"
-tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
-typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
-
-[package.extras]
-colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.10)"]
-jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
-uvloop = ["uvloop (>=0.15.2)"]
-
[[package]]
name = "certifi"
-version = "2025.10.5"
+version = "2025.11.12"
description = "Python package for providing Mozilla's CA Bundle."
optional = false
python-versions = ">=3.7"
groups = ["main", "dev"]
files = [
- {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"},
- {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"},
+ {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"},
+ {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"},
]
[[package]]
@@ -269,14 +220,14 @@ files = [
[[package]]
name = "click"
-version = "8.3.0"
+version = "8.3.1"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.10"
groups = ["main", "dev", "lint-and-format", "type-checking"]
files = [
- {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"},
- {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"},
+ {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"},
+ {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"},
]
[package.dependencies]
@@ -297,104 +248,104 @@ markers = {main = "platform_system == \"Windows\"", lint-and-format = "platform_
[[package]]
name = "coverage"
-version = "7.11.0"
+version = "7.13.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "coverage-7.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eb53f1e8adeeb2e78962bade0c08bfdc461853c7969706ed901821e009b35e31"},
- {file = "coverage-7.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9a03ec6cb9f40a5c360f138b88266fd8f58408d71e89f536b4f91d85721d075"},
- {file = "coverage-7.11.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0d7f0616c557cbc3d1c2090334eddcbb70e1ae3a40b07222d62b3aa47f608fab"},
- {file = "coverage-7.11.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e44a86a47bbdf83b0a3ea4d7df5410d6b1a0de984fbd805fa5101f3624b9abe0"},
- {file = "coverage-7.11.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:596763d2f9a0ee7eec6e643e29660def2eef297e1de0d334c78c08706f1cb785"},
- {file = "coverage-7.11.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ef55537ff511b5e0a43edb4c50a7bf7ba1c3eea20b4f49b1490f1e8e0e42c591"},
- {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9cbabd8f4d0d3dc571d77ae5bdbfa6afe5061e679a9d74b6797c48d143307088"},
- {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e24045453384e0ae2a587d562df2a04d852672eb63051d16096d3f08aa4c7c2f"},
- {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:7161edd3426c8d19bdccde7d49e6f27f748f3c31cc350c5de7c633fea445d866"},
- {file = "coverage-7.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d4ed4de17e692ba6415b0587bc7f12bc80915031fc9db46a23ce70fc88c9841"},
- {file = "coverage-7.11.0-cp310-cp310-win32.whl", hash = "sha256:765c0bc8fe46f48e341ef737c91c715bd2a53a12792592296a095f0c237e09cf"},
- {file = "coverage-7.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:24d6f3128f1b2d20d84b24f4074475457faedc3d4613a7e66b5e769939c7d969"},
- {file = "coverage-7.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d58ecaa865c5b9fa56e35efc51d1014d4c0d22838815b9fce57a27dd9576847"},
- {file = "coverage-7.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b679e171f1c104a5668550ada700e3c4937110dbdd153b7ef9055c4f1a1ee3cc"},
- {file = "coverage-7.11.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca61691ba8c5b6797deb221a0d09d7470364733ea9c69425a640f1f01b7c5bf0"},
- {file = "coverage-7.11.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:aef1747ede4bd8ca9cfc04cc3011516500c6891f1b33a94add3253f6f876b7b7"},
- {file = "coverage-7.11.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a1839d08406e4cba2953dcc0ffb312252f14d7c4c96919f70167611f4dee2623"},
- {file = "coverage-7.11.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e0eb0a2dcc62478eb5b4cbb80b97bdee852d7e280b90e81f11b407d0b81c4287"},
- {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bc1fbea96343b53f65d5351d8fd3b34fd415a2670d7c300b06d3e14a5af4f552"},
- {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:214b622259dd0cf435f10241f1333d32caa64dbc27f8790ab693428a141723de"},
- {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:258d9967520cca899695d4eb7ea38be03f06951d6ca2f21fb48b1235f791e601"},
- {file = "coverage-7.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cf9e6ff4ca908ca15c157c409d608da77a56a09877b97c889b98fb2c32b6465e"},
- {file = "coverage-7.11.0-cp311-cp311-win32.whl", hash = "sha256:fcc15fc462707b0680cff6242c48625da7f9a16a28a41bb8fd7a4280920e676c"},
- {file = "coverage-7.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:865965bf955d92790f1facd64fe7ff73551bd2c1e7e6b26443934e9701ba30b9"},
- {file = "coverage-7.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:5693e57a065760dcbeb292d60cc4d0231a6d4b6b6f6a3191561e1d5e8820b745"},
- {file = "coverage-7.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9c49e77811cf9d024b95faf86c3f059b11c0c9be0b0d61bc598f453703bd6fd1"},
- {file = "coverage-7.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a61e37a403a778e2cda2a6a39abcc895f1d984071942a41074b5c7ee31642007"},
- {file = "coverage-7.11.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:c79cae102bb3b1801e2ef1511fb50e91ec83a1ce466b2c7c25010d884336de46"},
- {file = "coverage-7.11.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:16ce17ceb5d211f320b62df002fa7016b7442ea0fd260c11cec8ce7730954893"},
- {file = "coverage-7.11.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:80027673e9d0bd6aef86134b0771845e2da85755cf686e7c7c59566cf5a89115"},
- {file = "coverage-7.11.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4d3ffa07a08657306cd2215b0da53761c4d73cb54d9143b9303a6481ec0cd415"},
- {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a3b6a5f8b2524fd6c1066bc85bfd97e78709bb5e37b5b94911a6506b65f47186"},
- {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fcc0a4aa589de34bc56e1a80a740ee0f8c47611bdfb28cd1849de60660f3799d"},
- {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:dba82204769d78c3fd31b35c3d5f46e06511936c5019c39f98320e05b08f794d"},
- {file = "coverage-7.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:81b335f03ba67309a95210caf3eb43bd6fe75a4e22ba653ef97b4696c56c7ec2"},
- {file = "coverage-7.11.0-cp312-cp312-win32.whl", hash = "sha256:037b2d064c2f8cc8716fe4d39cb705779af3fbf1ba318dc96a1af858888c7bb5"},
- {file = "coverage-7.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d66c0104aec3b75e5fd897e7940188ea1892ca1d0235316bf89286d6a22568c0"},
- {file = "coverage-7.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:d91ebeac603812a09cf6a886ba6e464f3bbb367411904ae3790dfe28311b15ad"},
- {file = "coverage-7.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:cc3f49e65ea6e0d5d9bd60368684fe52a704d46f9e7fc413918f18d046ec40e1"},
- {file = "coverage-7.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f39ae2f63f37472c17b4990f794035c9890418b1b8cca75c01193f3c8d3e01be"},
- {file = "coverage-7.11.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7db53b5cdd2917b6eaadd0b1251cf4e7d96f4a8d24e174bdbdf2f65b5ea7994d"},
- {file = "coverage-7.11.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10ad04ac3a122048688387828b4537bc9cf60c0bf4869c1e9989c46e45690b82"},
- {file = "coverage-7.11.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4036cc9c7983a2b1f2556d574d2eb2154ac6ed55114761685657e38782b23f52"},
- {file = "coverage-7.11.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7ab934dd13b1c5e94b692b1e01bd87e4488cb746e3a50f798cb9464fd128374b"},
- {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59a6e5a265f7cfc05f76e3bb53eca2e0dfe90f05e07e849930fecd6abb8f40b4"},
- {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:df01d6c4c81e15a7c88337b795bb7595a8596e92310266b5072c7e301168efbd"},
- {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:8c934bd088eed6174210942761e38ee81d28c46de0132ebb1801dbe36a390dcc"},
- {file = "coverage-7.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a03eaf7ec24078ad64a07f02e30060aaf22b91dedf31a6b24d0d98d2bba7f48"},
- {file = "coverage-7.11.0-cp313-cp313-win32.whl", hash = "sha256:695340f698a5f56f795b2836abe6fb576e7c53d48cd155ad2f80fd24bc63a040"},
- {file = "coverage-7.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2727d47fce3ee2bac648528e41455d1b0c46395a087a229deac75e9f88ba5a05"},
- {file = "coverage-7.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:0efa742f431529699712b92ecdf22de8ff198df41e43aeaaadf69973eb93f17a"},
- {file = "coverage-7.11.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:587c38849b853b157706407e9ebdca8fd12f45869edb56defbef2daa5fb0812b"},
- {file = "coverage-7.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b971bdefdd75096163dd4261c74be813c4508477e39ff7b92191dea19f24cd37"},
- {file = "coverage-7.11.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:269bfe913b7d5be12ab13a95f3a76da23cf147be7fa043933320ba5625f0a8de"},
- {file = "coverage-7.11.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:dadbcce51a10c07b7c72b0ce4a25e4b6dcb0c0372846afb8e5b6307a121eb99f"},
- {file = "coverage-7.11.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ed43fa22c6436f7957df036331f8fe4efa7af132054e1844918866cd228af6c"},
- {file = "coverage-7.11.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9516add7256b6713ec08359b7b05aeff8850c98d357784c7205b2e60aa2513fa"},
- {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb92e47c92fcbcdc692f428da67db33337fa213756f7adb6a011f7b5a7a20740"},
- {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:d06f4fc7acf3cabd6d74941d53329e06bab00a8fe10e4df2714f0b134bfc64ef"},
- {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:6fbcee1a8f056af07ecd344482f711f563a9eb1c2cad192e87df00338ec3cdb0"},
- {file = "coverage-7.11.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dbbf012be5f32533a490709ad597ad8a8ff80c582a95adc8d62af664e532f9ca"},
- {file = "coverage-7.11.0-cp313-cp313t-win32.whl", hash = "sha256:cee6291bb4fed184f1c2b663606a115c743df98a537c969c3c64b49989da96c2"},
- {file = "coverage-7.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a386c1061bf98e7ea4758e4313c0ab5ecf57af341ef0f43a0bf26c2477b5c268"},
- {file = "coverage-7.11.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f9ea02ef40bb83823b2b04964459d281688fe173e20643870bb5d2edf68bc836"},
- {file = "coverage-7.11.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c770885b28fb399aaf2a65bbd1c12bf6f307ffd112d6a76c5231a94276f0c497"},
- {file = "coverage-7.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a3d0e2087dba64c86a6b254f43e12d264b636a39e88c5cc0a01a7c71bcfdab7e"},
- {file = "coverage-7.11.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:73feb83bb41c32811973b8565f3705caf01d928d972b72042b44e97c71fd70d1"},
- {file = "coverage-7.11.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c6f31f281012235ad08f9a560976cc2fc9c95c17604ff3ab20120fe480169bca"},
- {file = "coverage-7.11.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9570ad567f880ef675673992222746a124b9595506826b210fbe0ce3f0499cd"},
- {file = "coverage-7.11.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8badf70446042553a773547a61fecaa734b55dc738cacf20c56ab04b77425e43"},
- {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a09c1211959903a479e389685b7feb8a17f59ec5a4ef9afde7650bd5eabc2777"},
- {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:5ef83b107f50db3f9ae40f69e34b3bd9337456c5a7fe3461c7abf8b75dd666a2"},
- {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f91f927a3215b8907e214af77200250bb6aae36eca3f760f89780d13e495388d"},
- {file = "coverage-7.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cdbcd376716d6b7fbfeedd687a6c4be019c5a5671b35f804ba76a4c0a778cba4"},
- {file = "coverage-7.11.0-cp314-cp314-win32.whl", hash = "sha256:bab7ec4bb501743edc63609320aaec8cd9188b396354f482f4de4d40a9d10721"},
- {file = "coverage-7.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:3d4ba9a449e9364a936a27322b20d32d8b166553bfe63059bd21527e681e2fad"},
- {file = "coverage-7.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:ce37f215223af94ef0f75ac68ea096f9f8e8c8ec7d6e8c346ee45c0d363f0479"},
- {file = "coverage-7.11.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:f413ce6e07e0d0dc9c433228727b619871532674b45165abafe201f200cc215f"},
- {file = "coverage-7.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:05791e528a18f7072bf5998ba772fe29db4da1234c45c2087866b5ba4dea710e"},
- {file = "coverage-7.11.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cacb29f420cfeb9283b803263c3b9a068924474ff19ca126ba9103e1278dfa44"},
- {file = "coverage-7.11.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314c24e700d7027ae3ab0d95fbf8d53544fca1f20345fd30cd219b737c6e58d3"},
- {file = "coverage-7.11.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:630d0bd7a293ad2fc8b4b94e5758c8b2536fdf36c05f1681270203e463cbfa9b"},
- {file = "coverage-7.11.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e89641f5175d65e2dbb44db15fe4ea48fade5d5bbb9868fdc2b4fce22f4a469d"},
- {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c9f08ea03114a637dab06cedb2e914da9dc67fa52c6015c018ff43fdde25b9c2"},
- {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:ce9f3bde4e9b031eaf1eb61df95c1401427029ea1bfddb8621c1161dcb0fa02e"},
- {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:e4dc07e95495923d6fd4d6c27bf70769425b71c89053083843fd78f378558996"},
- {file = "coverage-7.11.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:424538266794db2861db4922b05d729ade0940ee69dcf0591ce8f69784db0e11"},
- {file = "coverage-7.11.0-cp314-cp314t-win32.whl", hash = "sha256:4c1eeb3fb8eb9e0190bebafd0462936f75717687117339f708f395fe455acc73"},
- {file = "coverage-7.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b56efee146c98dbf2cf5cffc61b9829d1e94442df4d7398b26892a53992d3547"},
- {file = "coverage-7.11.0-cp314-cp314t-win_arm64.whl", hash = "sha256:b5c2705afa83f49bd91962a4094b6b082f94aef7626365ab3f8f4bd159c5acf3"},
- {file = "coverage-7.11.0-py3-none-any.whl", hash = "sha256:4b7589765348d78fb4e5fb6ea35d07564e387da2fc5efff62e0222971f155f68"},
- {file = "coverage-7.11.0.tar.gz", hash = "sha256:167bd504ac1ca2af7ff3b81d245dfea0292c5032ebef9d66cc08a7d28c1b8050"},
+ {file = "coverage-7.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:02d9fb9eccd48f6843c98a37bd6817462f130b86da8660461e8f5e54d4c06070"},
+ {file = "coverage-7.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:367449cf07d33dc216c083f2036bb7d976c6e4903ab31be400ad74ad9f85ce98"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cdb3c9f8fef0a954c632f64328a3935988d33a6604ce4bf67ec3e39670f12ae5"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d10fd186aac2316f9bbb46ef91977f9d394ded67050ad6d84d94ed6ea2e8e54e"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f88ae3e69df2ab62fb0bc5219a597cb890ba5c438190ffa87490b315190bb33"},
+ {file = "coverage-7.13.0-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4be718e51e86f553bcf515305a158a1cd180d23b72f07ae76d6017c3cc5d791"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a00d3a393207ae12f7c49bb1c113190883b500f48979abb118d8b72b8c95c032"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3a7b1cd820e1b6116f92c6128f1188e7afe421c7e1b35fa9836b11444e53ebd9"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:37eee4e552a65866f15dedd917d5e5f3d59805994260720821e2c1b51ac3248f"},
+ {file = "coverage-7.13.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:62d7c4f13102148c78d7353c6052af6d899a7f6df66a32bddcc0c0eb7c5326f8"},
+ {file = "coverage-7.13.0-cp310-cp310-win32.whl", hash = "sha256:24e4e56304fdb56f96f80eabf840eab043b3afea9348b88be680ec5986780a0f"},
+ {file = "coverage-7.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:74c136e4093627cf04b26a35dab8cbfc9b37c647f0502fc313376e11726ba303"},
+ {file = "coverage-7.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0dfa3855031070058add1a59fdfda0192fd3e8f97e7c81de0596c145dea51820"},
+ {file = "coverage-7.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4fdb6f54f38e334db97f72fa0c701e66d8479af0bc3f9bfb5b90f1c30f54500f"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7e442c013447d1d8d195be62852270b78b6e255b79b8675bad8479641e21fd96"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ed5630d946859de835a85e9a43b721123a8a44ec26e2830b296d478c7fd4259"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7f15a931a668e58087bc39d05d2b4bf4b14ff2875b49c994bbdb1c2217a8daeb"},
+ {file = "coverage-7.13.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30a3a201a127ea57f7e14ba43c93c9c4be8b7d17a26e03bb49e6966d019eede9"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7a485ff48fbd231efa32d58f479befce52dcb6bfb2a88bb7bf9a0b89b1bc8030"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:22486cdafba4f9e471c816a2a5745337742a617fef68e890d8baf9f3036d7833"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:263c3dbccc78e2e331e59e90115941b5f53e85cfcc6b3b2fbff1fd4e3d2c6ea8"},
+ {file = "coverage-7.13.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e5330fa0cc1f5c3c4c3bb8e101b742025933e7848989370a1d4c8c5e401ea753"},
+ {file = "coverage-7.13.0-cp311-cp311-win32.whl", hash = "sha256:0f4872f5d6c54419c94c25dd6ae1d015deeb337d06e448cd890a1e89a8ee7f3b"},
+ {file = "coverage-7.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51a202e0f80f241ccb68e3e26e19ab5b3bf0f813314f2c967642f13ebcf1ddfe"},
+ {file = "coverage-7.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:d2a9d7f1c11487b1c69367ab3ac2d81b9b3721f097aa409a3191c3e90f8f3dd7"},
+ {file = "coverage-7.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0b3d67d31383c4c68e19a88e28fc4c2e29517580f1b0ebec4a069d502ce1e0bf"},
+ {file = "coverage-7.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:581f086833d24a22c89ae0fe2142cfaa1c92c930adf637ddf122d55083fb5a0f"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a3a30f0e257df382f5f9534d4ce3d4cf06eafaf5192beb1a7bd066cb10e78fb"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:583221913fbc8f53b88c42e8dbb8fca1d0f2e597cb190ce45916662b8b9d9621"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f5d9bd30756fff3e7216491a0d6d520c448d5124d3d8e8f56446d6412499e74"},
+ {file = "coverage-7.13.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a23e5a1f8b982d56fa64f8e442e037f6ce29322f1f9e6c2344cd9e9f4407ee57"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b01c22bc74a7fb44066aaf765224c0d933ddf1f5047d6cdfe4795504a4493f8"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:898cce66d0836973f48dda4e3514d863d70142bdf6dfab932b9b6a90ea5b222d"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:3ab483ea0e251b5790c2aac03acde31bff0c736bf8a86829b89382b407cd1c3b"},
+ {file = "coverage-7.13.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d84e91521c5e4cb6602fe11ece3e1de03b2760e14ae4fcf1a4b56fa3c801fcd"},
+ {file = "coverage-7.13.0-cp312-cp312-win32.whl", hash = "sha256:193c3887285eec1dbdb3f2bd7fbc351d570ca9c02ca756c3afbc71b3c98af6ef"},
+ {file = "coverage-7.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:4f3e223b2b2db5e0db0c2b97286aba0036ca000f06aca9b12112eaa9af3d92ae"},
+ {file = "coverage-7.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:086cede306d96202e15a4b77ace8472e39d9f4e5f9fd92dd4fecdfb2313b2080"},
+ {file = "coverage-7.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:28ee1c96109974af104028a8ef57cec21447d42d0e937c0275329272e370ebcf"},
+ {file = "coverage-7.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d1e97353dcc5587b85986cda4ff3ec98081d7e84dd95e8b2a6d59820f0545f8a"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:99acd4dfdfeb58e1937629eb1ab6ab0899b131f183ee5f23e0b5da5cba2fec74"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ff45e0cd8451e293b63ced93161e189780baf444119391b3e7d25315060368a6"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f4f72a85316d8e13234cafe0a9f81b40418ad7a082792fa4165bd7d45d96066b"},
+ {file = "coverage-7.13.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:11c21557d0e0a5a38632cbbaca5f008723b26a89d70db6315523df6df77d6232"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76541dc8d53715fb4f7a3a06b34b0dc6846e3c69bc6204c55653a85dd6220971"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6e9e451dee940a86789134b6b0ffbe31c454ade3b849bb8a9d2cca2541a8e91d"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5c67dace46f361125e6b9cace8fe0b729ed8479f47e70c89b838d319375c8137"},
+ {file = "coverage-7.13.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f59883c643cb19630500f57016f76cfdcd6845ca8c5b5ea1f6e17f74c8e5f511"},
+ {file = "coverage-7.13.0-cp313-cp313-win32.whl", hash = "sha256:58632b187be6f0be500f553be41e277712baa278147ecb7559983c6d9faf7ae1"},
+ {file = "coverage-7.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:73419b89f812f498aca53f757dd834919b48ce4799f9d5cad33ca0ae442bdb1a"},
+ {file = "coverage-7.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:eb76670874fdd6091eedcc856128ee48c41a9bbbb9c3f1c7c3cf169290e3ffd6"},
+ {file = "coverage-7.13.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6e63ccc6e0ad8986386461c3c4b737540f20426e7ec932f42e030320896c311a"},
+ {file = "coverage-7.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:494f5459ffa1bd45e18558cd98710c36c0b8fbfa82a5eabcbe671d80ecffbfe8"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:06cac81bf10f74034e055e903f5f946e3e26fc51c09fc9f584e4a1605d977053"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f2ffc92b46ed6e6760f1d47a71e56b5664781bc68986dbd1836b2b70c0ce2071"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0602f701057c6823e5db1b74530ce85f17c3c5be5c85fc042ac939cbd909426e"},
+ {file = "coverage-7.13.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:25dc33618d45456ccb1d37bce44bc78cf269909aa14c4db2e03d63146a8a1493"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:71936a8b3b977ddd0b694c28c6a34f4fff2e9dd201969a4ff5d5fc7742d614b0"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:936bc20503ce24770c71938d1369461f0c5320830800933bc3956e2a4ded930e"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:af0a583efaacc52ae2521f8d7910aff65cdb093091d76291ac5820d5e947fc1c"},
+ {file = "coverage-7.13.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f1c23e24a7000da892a312fb17e33c5f94f8b001de44b7cf8ba2e36fbd15859e"},
+ {file = "coverage-7.13.0-cp313-cp313t-win32.whl", hash = "sha256:5f8a0297355e652001015e93be345ee54393e45dc3050af4a0475c5a2b767d46"},
+ {file = "coverage-7.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6abb3a4c52f05e08460bd9acf04fec027f8718ecaa0d09c40ffbc3fbd70ecc39"},
+ {file = "coverage-7.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:3ad968d1e3aa6ce5be295ab5fe3ae1bf5bb4769d0f98a80a0252d543a2ef2e9e"},
+ {file = "coverage-7.13.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:453b7ec753cf5e4356e14fe858064e5520c460d3bbbcb9c35e55c0d21155c256"},
+ {file = "coverage-7.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af827b7cbb303e1befa6c4f94fd2bf72f108089cfa0f8abab8f4ca553cf5ca5a"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9987a9e4f8197a1000280f7cc089e3ea2c8b3c0a64d750537809879a7b4ceaf9"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3188936845cd0cb114fa6a51842a304cdbac2958145d03be2377ec41eb285d19"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2bdb3babb74079f021696cb46b8bb5f5661165c385d3a238712b031a12355be"},
+ {file = "coverage-7.13.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7464663eaca6adba4175f6c19354feea61ebbdd735563a03d1e472c7072d27bb"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8069e831f205d2ff1f3d355e82f511eb7c5522d7d413f5db5756b772ec8697f8"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6fb2d5d272341565f08e962cce14cdf843a08ac43bd621783527adb06b089c4b"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:5e70f92ef89bac1ac8a99b3324923b4749f008fdbd7aa9cb35e01d7a284a04f9"},
+ {file = "coverage-7.13.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4b5de7d4583e60d5fd246dd57fcd3a8aa23c6e118a8c72b38adf666ba8e7e927"},
+ {file = "coverage-7.13.0-cp314-cp314-win32.whl", hash = "sha256:a6c6e16b663be828a8f0b6c5027d36471d4a9f90d28444aa4ced4d48d7d6ae8f"},
+ {file = "coverage-7.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:0900872f2fdb3ee5646b557918d02279dc3af3dfb39029ac4e945458b13f73bc"},
+ {file = "coverage-7.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:3a10260e6a152e5f03f26db4a407c4c62d3830b9af9b7c0450b183615f05d43b"},
+ {file = "coverage-7.13.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:9097818b6cc1cfb5f174e3263eba4a62a17683bcfe5c4b5d07f4c97fa51fbf28"},
+ {file = "coverage-7.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0018f73dfb4301a89292c73be6ba5f58722ff79f51593352759c1790ded1cabe"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:166ad2a22ee770f5656e1257703139d3533b4a0b6909af67c6b4a3adc1c98657"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f6aaef16d65d1787280943f1c8718dc32e9cf141014e4634d64446702d26e0ff"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e999e2dcc094002d6e2c7bbc1fb85b58ba4f465a760a8014d97619330cdbbbf3"},
+ {file = "coverage-7.13.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:00c3d22cf6fb1cf3bf662aaaa4e563be8243a5ed2630339069799835a9cc7f9b"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:22ccfe8d9bb0d6134892cbe1262493a8c70d736b9df930f3f3afae0fe3ac924d"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:9372dff5ea15930fea0445eaf37bbbafbc771a49e70c0aeed8b4e2c2614cc00e"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:69ac2c492918c2461bc6ace42d0479638e60719f2a4ef3f0815fa2df88e9f940"},
+ {file = "coverage-7.13.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:739c6c051a7540608d097b8e13c76cfa85263ced467168dc6b477bae3df7d0e2"},
+ {file = "coverage-7.13.0-cp314-cp314t-win32.whl", hash = "sha256:fe81055d8c6c9de76d60c94ddea73c290b416e061d40d542b24a5871bad498b7"},
+ {file = "coverage-7.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:445badb539005283825959ac9fa4a28f712c214b65af3a2c464f1adc90f5fcbc"},
+ {file = "coverage-7.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:de7f6748b890708578fc4b7bb967d810aeb6fcc9bff4bb77dbca77dab2f9df6a"},
+ {file = "coverage-7.13.0-py3-none-any.whl", hash = "sha256:850d2998f380b1e266459ca5b47bc9e7daf9af1d070f66317972f382d46f1904"},
+ {file = "coverage-7.13.0.tar.gz", hash = "sha256:a394aa27f2d7ff9bc04cf703817773a59ad6dfbd577032e690f961d2460ee936"},
]
[package.dependencies]
@@ -433,27 +384,27 @@ profile = ["gprof2dot (>=2022.7.29)"]
[[package]]
name = "docutils"
-version = "0.22.2"
+version = "0.22.4"
description = "Docutils -- Python Documentation Utilities"
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8"},
- {file = "docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d"},
+ {file = "docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de"},
+ {file = "docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968"},
]
[[package]]
name = "exceptiongroup"
-version = "1.3.0"
+version = "1.3.1"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
groups = ["dev"]
-markers = "python_version == \"3.10\""
+markers = "python_version < \"3.11\""
files = [
- {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
- {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
+ {file = "exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598"},
+ {file = "exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219"},
]
[package.dependencies]
@@ -464,14 +415,14 @@ test = ["pytest (>=6)"]
[[package]]
name = "faker"
-version = "37.12.0"
+version = "39.0.0"
description = "Faker is a Python package that generates fake data for you."
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "faker-37.12.0-py3-none-any.whl", hash = "sha256:afe7ccc038da92f2fbae30d8e16d19d91e92e242f8401ce9caf44de892bab4c4"},
- {file = "faker-37.12.0.tar.gz", hash = "sha256:7505e59a7e02fa9010f06c3e1e92f8250d4cfbb30632296140c2d6dbef09b0fa"},
+ {file = "faker-39.0.0-py3-none-any.whl", hash = "sha256:c72f1fca8f1a24b8da10fcaa45739135a19772218ddd61b86b7ea1b8c790dce7"},
+ {file = "faker-39.0.0.tar.gz", hash = "sha256:ddae46d3b27e01cea7894651d687b33bcbe19a45ef044042c721ceac6d3da0ff"},
]
[package.dependencies]
@@ -479,37 +430,37 @@ tzdata = "*"
[[package]]
name = "fastapi"
-version = "0.120.4"
+version = "0.127.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["dev"]
files = [
- {file = "fastapi-0.120.4-py3-none-any.whl", hash = "sha256:9bdf192308676480d3593e10fd05094e56d6fdc7d9283db26053d8104d5f82a0"},
- {file = "fastapi-0.120.4.tar.gz", hash = "sha256:2d856bc847893ca4d77896d4504ffdec0fb04312b705065fca9104428eca3868"},
+ {file = "fastapi-0.127.0-py3-none-any.whl", hash = "sha256:725aa2bb904e2eff8031557cf4b9b77459bfedd63cae8427634744fd199f6a49"},
+ {file = "fastapi-0.127.0.tar.gz", hash = "sha256:5a9246e03dcd1fdb19f1396db30894867c1d630f5107dc167dcbc5ed1ea7d259"},
]
[package.dependencies]
annotated-doc = ">=0.0.2"
-pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
-starlette = ">=0.40.0,<0.50.0"
+pydantic = ">=2.7.0"
+starlette = ">=0.40.0,<0.51.0"
typing-extensions = ">=4.8.0"
[package.extras]
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
-standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
-standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
+standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
+standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "genbadge"
-version = "1.1.2"
+version = "1.1.3"
description = "Generate badges for tools that do not provide one."
optional = false
python-versions = "*"
groups = ["dev"]
files = [
- {file = "genbadge-1.1.2-py2.py3-none-any.whl", hash = "sha256:4e3073cb56c2745fbef4b7da97eb85b28a18a22af519b66acb6706b6546279f1"},
- {file = "genbadge-1.1.2.tar.gz", hash = "sha256:987ed2feaf6e9cc2850fc3883320d8706b3849eb6c9f436156254dcac438515c"},
+ {file = "genbadge-1.1.3-py2.py3-none-any.whl", hash = "sha256:6e4316c171c6f0f84becae4eb116258340bdc054458632abc622d36b8040655e"},
+ {file = "genbadge-1.1.3.tar.gz", hash = "sha256:2292ea9cc20af4463dfde952c6b15544fdab9d6e50945f63a42cc400c521fa74"},
]
[package.dependencies]
@@ -719,6 +670,93 @@ files = [
{file = "lazy_object_proxy-1.12.0.tar.gz", hash = "sha256:1f5a462d92fd0cfb82f1fab28b51bfb209fabbe6aabf7f0d51472c0c124c0c61"},
]
+[[package]]
+name = "librt"
+version = "0.7.4"
+description = "Mypyc runtime library"
+optional = false
+python-versions = ">=3.9"
+groups = ["type-checking"]
+markers = "platform_python_implementation != \"PyPy\""
+files = [
+ {file = "librt-0.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dc300cb5a5a01947b1ee8099233156fdccd5001739e5f596ecfbc0dab07b5a3b"},
+ {file = "librt-0.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee8d3323d921e0f6919918a97f9b5445a7dfe647270b2629ec1008aa676c0bc0"},
+ {file = "librt-0.7.4-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:95cb80854a355b284c55f79674f6187cc9574df4dc362524e0cce98c89ee8331"},
+ {file = "librt-0.7.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca1caedf8331d8ad6027f93b52d68ed8f8009f5c420c246a46fe9d3be06be0f"},
+ {file = "librt-0.7.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2a6f1236151e6fe1da289351b5b5bce49651c91554ecc7b70a947bced6fe212"},
+ {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7766b57aeebaf3f1dac14fdd4a75c9a61f2ed56d8ebeefe4189db1cb9d2a3783"},
+ {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:1c4c89fb01157dd0a3bfe9e75cd6253b0a1678922befcd664eca0772a4c6c979"},
+ {file = "librt-0.7.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f7fa8beef580091c02b4fd26542de046b2abfe0aaefa02e8bcf68acb7618f2b3"},
+ {file = "librt-0.7.4-cp310-cp310-win32.whl", hash = "sha256:543c42fa242faae0466fe72d297976f3c710a357a219b1efde3a0539a68a6997"},
+ {file = "librt-0.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:25cc40d8eb63f0a7ea4c8f49f524989b9df901969cb860a2bc0e4bad4b8cb8a8"},
+ {file = "librt-0.7.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3485b9bb7dfa66167d5500ffdafdc35415b45f0da06c75eb7df131f3357b174a"},
+ {file = "librt-0.7.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:188b4b1a770f7f95ea035d5bbb9d7367248fc9d12321deef78a269ebf46a5729"},
+ {file = "librt-0.7.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1b668b1c840183e4e38ed5a99f62fac44c3a3eef16870f7f17cfdfb8b47550ed"},
+ {file = "librt-0.7.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0e8f864b521f6cfedb314d171630f827efee08f5c3462bcbc2244ab8e1768cd6"},
+ {file = "librt-0.7.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df7c9def4fc619a9c2ab402d73a0c5b53899abe090e0100323b13ccb5a3dd82"},
+ {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f79bc3595b6ed159a1bf0cdc70ed6ebec393a874565cab7088a219cca14da727"},
+ {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77772a4b8b5f77d47d883846928c36d730b6e612a6388c74cba33ad9eb149c11"},
+ {file = "librt-0.7.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:064a286e6ab0b4c900e228ab4fa9cb3811b4b83d3e0cc5cd816b2d0f548cb61c"},
+ {file = "librt-0.7.4-cp311-cp311-win32.whl", hash = "sha256:42da201c47c77b6cc91fc17e0e2b330154428d35d6024f3278aa2683e7e2daf2"},
+ {file = "librt-0.7.4-cp311-cp311-win_amd64.whl", hash = "sha256:d31acb5886c16ae1711741f22504195af46edec8315fe69b77e477682a87a83e"},
+ {file = "librt-0.7.4-cp311-cp311-win_arm64.whl", hash = "sha256:114722f35093da080a333b3834fff04ef43147577ed99dd4db574b03a5f7d170"},
+ {file = "librt-0.7.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd3b5c37e0fb6666c27cf4e2c88ae43da904f2155c4cfc1e5a2fdce3b9fcf92"},
+ {file = "librt-0.7.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c5de1928c486201b23ed0cc4ac92e6e07be5cd7f3abc57c88a9cf4f0f32108"},
+ {file = "librt-0.7.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:078ae52ffb3f036396cc4aed558e5b61faedd504a3c1f62b8ae34bf95ae39d94"},
+ {file = "librt-0.7.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce58420e25097b2fc201aef9b9f6d65df1eb8438e51154e1a7feb8847e4a55ab"},
+ {file = "librt-0.7.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b719c8730c02a606dc0e8413287e8e94ac2d32a51153b300baf1f62347858fba"},
+ {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3749ef74c170809e6dee68addec9d2458700a8de703de081c888e92a8b015cf9"},
+ {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b35c63f557653c05b5b1b6559a074dbabe0afee28ee2a05b6c9ba21ad0d16a74"},
+ {file = "librt-0.7.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1ef704e01cb6ad39ad7af668d51677557ca7e5d377663286f0ee1b6b27c28e5f"},
+ {file = "librt-0.7.4-cp312-cp312-win32.whl", hash = "sha256:c66c2b245926ec15188aead25d395091cb5c9df008d3b3207268cd65557d6286"},
+ {file = "librt-0.7.4-cp312-cp312-win_amd64.whl", hash = "sha256:71a56f4671f7ff723451f26a6131754d7c1809e04e22ebfbac1db8c9e6767a20"},
+ {file = "librt-0.7.4-cp312-cp312-win_arm64.whl", hash = "sha256:419eea245e7ec0fe664eb7e85e7ff97dcdb2513ca4f6b45a8ec4a3346904f95a"},
+ {file = "librt-0.7.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d44a1b1ba44cbd2fc3cb77992bef6d6fdb1028849824e1dd5e4d746e1f7f7f0b"},
+ {file = "librt-0.7.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c9cab4b3de1f55e6c30a84c8cee20e4d3b2476f4d547256694a1b0163da4fe32"},
+ {file = "librt-0.7.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:2857c875f1edd1feef3c371fbf830a61b632fb4d1e57160bb1e6a3206e6abe67"},
+ {file = "librt-0.7.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b370a77be0a16e1ad0270822c12c21462dc40496e891d3b0caf1617c8cc57e20"},
+ {file = "librt-0.7.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d05acd46b9a52087bfc50c59dfdf96a2c480a601e8898a44821c7fd676598f74"},
+ {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70969229cb23d9c1a80e14225838d56e464dc71fa34c8342c954fc50e7516dee"},
+ {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4450c354b89dbb266730893862dbff06006c9ed5b06b6016d529b2bf644fc681"},
+ {file = "librt-0.7.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:adefe0d48ad35b90b6f361f6ff5a1bd95af80c17d18619c093c60a20e7a5b60c"},
+ {file = "librt-0.7.4-cp313-cp313-win32.whl", hash = "sha256:21ea710e96c1e050635700695095962a22ea420d4b3755a25e4909f2172b4ff2"},
+ {file = "librt-0.7.4-cp313-cp313-win_amd64.whl", hash = "sha256:772e18696cf5a64afee908662fbcb1f907460ddc851336ee3a848ef7684c8e1e"},
+ {file = "librt-0.7.4-cp313-cp313-win_arm64.whl", hash = "sha256:52e34c6af84e12921748c8354aa6acf1912ca98ba60cdaa6920e34793f1a0788"},
+ {file = "librt-0.7.4-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4f1ee004942eaaed6e06c087d93ebc1c67e9a293e5f6b9b5da558df6bf23dc5d"},
+ {file = "librt-0.7.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d854c6dc0f689bad7ed452d2a3ecff58029d80612d336a45b62c35e917f42d23"},
+ {file = "librt-0.7.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a4f7339d9e445280f23d63dea842c0c77379c4a47471c538fc8feedab9d8d063"},
+ {file = "librt-0.7.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39003fc73f925e684f8521b2dbf34f61a5deb8a20a15dcf53e0d823190ce8848"},
+ {file = "librt-0.7.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6bb15ee29d95875ad697d449fe6071b67f730f15a6961913a2b0205015ca0843"},
+ {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:02a69369862099e37d00765583052a99d6a68af7e19b887e1b78fee0146b755a"},
+ {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ec72342cc4d62f38b25a94e28b9efefce41839aecdecf5e9627473ed04b7be16"},
+ {file = "librt-0.7.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:776dbb9bfa0fc5ce64234b446995d8d9f04badf64f544ca036bd6cff6f0732ce"},
+ {file = "librt-0.7.4-cp314-cp314-win32.whl", hash = "sha256:0f8cac84196d0ffcadf8469d9ded4d4e3a8b1c666095c2a291e22bf58e1e8a9f"},
+ {file = "librt-0.7.4-cp314-cp314-win_amd64.whl", hash = "sha256:037f5cb6fe5abe23f1dc058054d50e9699fcc90d0677eee4e4f74a8677636a1a"},
+ {file = "librt-0.7.4-cp314-cp314-win_arm64.whl", hash = "sha256:a5deebb53d7a4d7e2e758a96befcd8edaaca0633ae71857995a0f16033289e44"},
+ {file = "librt-0.7.4-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b4c25312c7f4e6ab35ab16211bdf819e6e4eddcba3b2ea632fb51c9a2a97e105"},
+ {file = "librt-0.7.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:618b7459bb392bdf373f2327e477597fff8f9e6a1878fffc1b711c013d1b0da4"},
+ {file = "librt-0.7.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1437c3f72a30c7047f16fd3e972ea58b90172c3c6ca309645c1c68984f05526a"},
+ {file = "librt-0.7.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c96cb76f055b33308f6858b9b594618f1b46e147a4d03a4d7f0c449e304b9b95"},
+ {file = "librt-0.7.4-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28f990e6821204f516d09dc39966ef8b84556ffd648d5926c9a3f681e8de8906"},
+ {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bc4aebecc79781a1b77d7d4e7d9fe080385a439e198d993b557b60f9117addaf"},
+ {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:022cc673e69283a42621dd453e2407cf1647e77f8bd857d7ad7499901e62376f"},
+ {file = "librt-0.7.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2b3ca211ae8ea540569e9c513da052699b7b06928dcda61247cb4f318122bdb5"},
+ {file = "librt-0.7.4-cp314-cp314t-win32.whl", hash = "sha256:8a461f6456981d8c8e971ff5a55f2e34f4e60871e665d2f5fde23ee74dea4eeb"},
+ {file = "librt-0.7.4-cp314-cp314t-win_amd64.whl", hash = "sha256:721a7b125a817d60bf4924e1eec2a7867bfcf64cfc333045de1df7a0629e4481"},
+ {file = "librt-0.7.4-cp314-cp314t-win_arm64.whl", hash = "sha256:76b2ba71265c0102d11458879b4d53ccd0b32b0164d14deb8d2b598a018e502f"},
+ {file = "librt-0.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6fc4aa67fedd827a601f97f0e61cc72711d0a9165f2c518e9a7c38fc1568b9ad"},
+ {file = "librt-0.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e710c983d29d9cc4da29113b323647db286eaf384746344f4a233708cca1a82c"},
+ {file = "librt-0.7.4-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:43a2515a33f2bc17b15f7fb49ff6426e49cb1d5b2539bc7f8126b9c5c7f37164"},
+ {file = "librt-0.7.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fd766bb9ace3498f6b93d32f30c0e7c8ce6b727fecbc84d28160e217bb66254"},
+ {file = "librt-0.7.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce1b44091355b68cffd16e2abac07c1cafa953fa935852d3a4dd8975044ca3bf"},
+ {file = "librt-0.7.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5a72b905420c4bb2c10c87b5c09fe6faf4a76d64730e3802feef255e43dfbf5a"},
+ {file = "librt-0.7.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:07c4d7c9305e75a0edd3427b79c7bd1d019cd7eddaa7c89dbb10e0c7946bffbb"},
+ {file = "librt-0.7.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2e734c2c54423c6dcc77f58a8585ba83b9f72e422f9edf09cab1096d4a4bdc82"},
+ {file = "librt-0.7.4-cp39-cp39-win32.whl", hash = "sha256:a34ae11315d4e26326aaf04e21ccd8d9b7de983635fba38d73e203a9c8e3fe3d"},
+ {file = "librt-0.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:7e4b5ffa1614ad4f32237d739699be444be28de95071bfa4e66a8da9fa777798"},
+ {file = "librt-0.7.4.tar.gz", hash = "sha256:3871af56c59864d5fd21d1ac001eb2fb3b140d52ba0454720f2e4a19812404ba"},
+]
+
[[package]]
name = "markdown-it-py"
version = "4.0.0"
@@ -880,53 +918,54 @@ files = [
[[package]]
name = "mypy"
-version = "1.18.2"
+version = "1.19.1"
description = "Optional static typing for Python"
optional = false
python-versions = ">=3.9"
groups = ["type-checking"]
files = [
- {file = "mypy-1.18.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c1eab0cf6294dafe397c261a75f96dc2c31bffe3b944faa24db5def4e2b0f77c"},
- {file = "mypy-1.18.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a780ca61fc239e4865968ebc5240bb3bf610ef59ac398de9a7421b54e4a207e"},
- {file = "mypy-1.18.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448acd386266989ef11662ce3c8011fd2a7b632e0ec7d61a98edd8e27472225b"},
- {file = "mypy-1.18.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f9e171c465ad3901dc652643ee4bffa8e9fef4d7d0eece23b428908c77a76a66"},
- {file = "mypy-1.18.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:592ec214750bc00741af1f80cbf96b5013d81486b7bb24cb052382c19e40b428"},
- {file = "mypy-1.18.2-cp310-cp310-win_amd64.whl", hash = "sha256:7fb95f97199ea11769ebe3638c29b550b5221e997c63b14ef93d2e971606ebed"},
- {file = "mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f"},
- {file = "mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341"},
- {file = "mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d"},
- {file = "mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86"},
- {file = "mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37"},
- {file = "mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8"},
- {file = "mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34"},
- {file = "mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764"},
- {file = "mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893"},
- {file = "mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914"},
- {file = "mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8"},
- {file = "mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074"},
- {file = "mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc"},
- {file = "mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e"},
- {file = "mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986"},
- {file = "mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d"},
- {file = "mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba"},
- {file = "mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544"},
- {file = "mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce"},
- {file = "mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d"},
- {file = "mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c"},
- {file = "mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb"},
- {file = "mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075"},
- {file = "mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf"},
- {file = "mypy-1.18.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:25a9c8fb67b00599f839cf472713f54249a62efd53a54b565eb61956a7e3296b"},
- {file = "mypy-1.18.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c2b9c7e284ee20e7598d6f42e13ca40b4928e6957ed6813d1ab6348aa3f47133"},
- {file = "mypy-1.18.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6985ed057513e344e43a26cc1cd815c7a94602fb6a3130a34798625bc2f07b6"},
- {file = "mypy-1.18.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22f27105f1525ec024b5c630c0b9f36d5c1cc4d447d61fe51ff4bd60633f47ac"},
- {file = "mypy-1.18.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:030c52d0ea8144e721e49b1f68391e39553d7451f0c3f8a7565b59e19fcb608b"},
- {file = "mypy-1.18.2-cp39-cp39-win_amd64.whl", hash = "sha256:aa5e07ac1a60a253445797e42b8b2963c9675563a94f11291ab40718b016a7a0"},
- {file = "mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e"},
- {file = "mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b"},
+ {file = "mypy-1.19.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f05aa3d375b385734388e844bc01733bd33c644ab48e9684faa54e5389775ec"},
+ {file = "mypy-1.19.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:022ea7279374af1a5d78dfcab853fe6a536eebfda4b59deab53cd21f6cd9f00b"},
+ {file = "mypy-1.19.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee4c11e460685c3e0c64a4c5de82ae143622410950d6be863303a1c4ba0e36d6"},
+ {file = "mypy-1.19.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de759aafbae8763283b2ee5869c7255391fbc4de3ff171f8f030b5ec48381b74"},
+ {file = "mypy-1.19.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ab43590f9cd5108f41aacf9fca31841142c786827a74ab7cc8a2eacb634e09a1"},
+ {file = "mypy-1.19.1-cp310-cp310-win_amd64.whl", hash = "sha256:2899753e2f61e571b3971747e302d5f420c3fd09650e1951e99f823bc3089dac"},
+ {file = "mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288"},
+ {file = "mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab"},
+ {file = "mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6"},
+ {file = "mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331"},
+ {file = "mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925"},
+ {file = "mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042"},
+ {file = "mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1"},
+ {file = "mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e"},
+ {file = "mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2"},
+ {file = "mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8"},
+ {file = "mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a"},
+ {file = "mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13"},
+ {file = "mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250"},
+ {file = "mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b"},
+ {file = "mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e"},
+ {file = "mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef"},
+ {file = "mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75"},
+ {file = "mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd"},
+ {file = "mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1"},
+ {file = "mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718"},
+ {file = "mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b"},
+ {file = "mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045"},
+ {file = "mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957"},
+ {file = "mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f"},
+ {file = "mypy-1.19.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7bcfc336a03a1aaa26dfce9fff3e287a3ba99872a157561cbfcebe67c13308e3"},
+ {file = "mypy-1.19.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b7951a701c07ea584c4fe327834b92a30825514c868b1f69c30445093fdd9d5a"},
+ {file = "mypy-1.19.1-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b13cfdd6c87fc3efb69ea4ec18ef79c74c3f98b4e5498ca9b85ab3b2c2329a67"},
+ {file = "mypy-1.19.1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f28f99c824ecebcdaa2e55d82953e38ff60ee5ec938476796636b86afa3956e"},
+ {file = "mypy-1.19.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c608937067d2fc5a4dd1a5ce92fd9e1398691b8c5d012d66e1ddd430e9244376"},
+ {file = "mypy-1.19.1-cp39-cp39-win_amd64.whl", hash = "sha256:409088884802d511ee52ca067707b90c883426bd95514e8cfda8281dc2effe24"},
+ {file = "mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247"},
+ {file = "mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba"},
]
[package.dependencies]
+librt = {version = ">=0.6.2", markers = "platform_python_implementation != \"PyPy\""}
mypy_extensions = ">=1.0.0"
pathspec = ">=0.9.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
@@ -945,7 +984,7 @@ version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.8"
-groups = ["main", "type-checking"]
+groups = ["type-checking"]
files = [
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
@@ -953,47 +992,46 @@ files = [
[[package]]
name = "nodeenv"
-version = "1.9.1"
+version = "1.10.0"
description = "Node.js virtual environment builder"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["type-checking"]
files = [
- {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
- {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
+ {file = "nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827"},
+ {file = "nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb"},
]
[[package]]
name = "openapi-core"
-version = "0.19.5"
+version = "0.22.0"
description = "client-side and server-side support for the OpenAPI Specification v3"
optional = false
-python-versions = "<4.0.0,>=3.8.0"
+python-versions = "<4.0.0,>=3.9.0"
groups = ["main"]
files = [
- {file = "openapi_core-0.19.5-py3-none-any.whl", hash = "sha256:ef7210e83a59394f46ce282639d8d26ad6fc8094aa904c9c16eb1bac8908911f"},
- {file = "openapi_core-0.19.5.tar.gz", hash = "sha256:421e753da56c391704454e66afe4803a290108590ac8fa6f4a4487f4ec11f2d3"},
+ {file = "openapi_core-0.22.0-py3-none-any.whl", hash = "sha256:8fb7c325f2db4ef6c60584b1870f90eeb3183aa47e30643715c5003b7677a149"},
+ {file = "openapi_core-0.22.0.tar.gz", hash = "sha256:b30490dfa74e3aac2276105525590135212352f5dd7e5acf8f62f6a89ed6f2d0"},
]
[package.dependencies]
isodate = "*"
-jsonschema = ">=4.18.0,<5.0.0"
-jsonschema-path = ">=0.3.1,<0.4.0"
+jsonschema = ">=4.23.0,<5.0.0"
+jsonschema-path = ">=0.3.4,<0.4.0"
more-itertools = "*"
openapi-schema-validator = ">=0.6.0,<0.7.0"
openapi-spec-validator = ">=0.7.1,<0.8.0"
-parse = "*"
typing-extensions = ">=4.8.0,<5.0.0"
-werkzeug = "<3.1.2"
+werkzeug = ">=2.1.0"
[package.extras]
aiohttp = ["aiohttp (>=3.0)", "multidict (>=6.0.4,<7.0.0)"]
django = ["django (>=3.0)"]
falcon = ["falcon (>=3.0)"]
-fastapi = ["fastapi (>=0.111,<0.116)"]
+fastapi = ["fastapi (>=0.111,<0.125)"]
flask = ["flask"]
requests = ["requests"]
-starlette = ["aioitertools (>=0.11,<0.13)", "starlette (>=0.26.1,<0.45.0)"]
+starlette = ["aioitertools (>=0.11,<0.14)", "starlette (>=0.26.1,<0.50.0)"]
[[package]]
name = "openapi-schema-validator"
@@ -1042,18 +1080,6 @@ files = [
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
]
-[[package]]
-name = "parse"
-version = "1.20.2"
-description = "parse() is the opposite of format()"
-optional = false
-python-versions = "*"
-groups = ["main"]
-files = [
- {file = "parse-1.20.2-py2.py3-none-any.whl", hash = "sha256:967095588cb802add9177d0c0b6133b5ba33b1ea9007ca800e526f42a85af558"},
- {file = "parse-1.20.2.tar.gz", hash = "sha256:b41d604d16503c79d81af5165155c0b20f6c8d6c559efa66b4b695c3e5a0a0ce"},
-]
-
[[package]]
name = "pathable"
version = "0.4.4"
@@ -1072,7 +1098,7 @@ version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
-groups = ["main", "lint-and-format", "type-checking"]
+groups = ["lint-and-format", "type-checking"]
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
@@ -1189,14 +1215,14 @@ xmp = ["defusedxml"]
[[package]]
name = "platformdirs"
-version = "4.5.0"
+version = "4.5.1"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.10"
-groups = ["main", "dev", "lint-and-format", "type-checking"]
+groups = ["dev", "lint-and-format", "type-checking"]
files = [
- {file = "platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3"},
- {file = "platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312"},
+ {file = "platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31"},
+ {file = "platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda"},
]
[package.extras]
@@ -1249,19 +1275,19 @@ ssv = ["swagger-spec-validator (>=3.0.4,<3.1.0)"]
[[package]]
name = "pydantic"
-version = "2.12.3"
+version = "2.12.5"
description = "Data validation using Python type hints"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
- {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"},
- {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"},
+ {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"},
+ {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"},
]
[package.dependencies]
annotated-types = ">=0.6.0"
-pydantic-core = "2.41.4"
+pydantic-core = "2.41.5"
typing-extensions = ">=4.14.1"
typing-inspection = ">=0.4.2"
@@ -1271,129 +1297,133 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows
[[package]]
name = "pydantic-core"
-version = "2.41.4"
+version = "2.41.5"
description = "Core functionality for Pydantic validation and serialization"
optional = false
python-versions = ">=3.9"
groups = ["main", "dev"]
files = [
- {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"},
- {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"},
- {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"},
- {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"},
- {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"},
- {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"},
- {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"},
- {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"},
- {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"},
- {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"},
- {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"},
- {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"},
- {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"},
- {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"},
- {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"},
- {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"},
- {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"},
- {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"},
- {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"},
- {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"},
- {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"},
- {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"},
- {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"},
- {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"},
- {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"},
- {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"},
- {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"},
- {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"},
- {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"},
- {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"},
- {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"},
- {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"},
- {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"},
- {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"},
- {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"},
- {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"},
- {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"},
- {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"},
- {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"},
- {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"},
- {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"},
- {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"},
- {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"},
- {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"},
- {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"},
- {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"},
- {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"},
- {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"},
- {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"},
- {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"},
- {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"},
- {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"},
- {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"},
- {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"},
- {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"},
- {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"},
- {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"},
- {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"},
- {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"},
- {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"},
- {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"},
- {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"},
- {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"},
- {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"},
- {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"},
- {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"},
- {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"},
- {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"},
- {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"},
- {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"},
- {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"},
- {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"},
- {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"},
- {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"},
- {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"},
- {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"},
- {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"},
- {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"},
- {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"},
- {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"},
- {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"},
- {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"},
- {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"},
- {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"},
- {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"},
- {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"},
- {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"},
- {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"},
- {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"},
- {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"},
- {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"},
- {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"},
- {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"},
- {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"},
- {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"},
- {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"},
- {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"},
- {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"},
- {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"},
- {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"},
- {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"},
- {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"},
- {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"},
+ {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"},
+ {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"},
+ {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"},
+ {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"},
+ {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"},
+ {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"},
+ {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"},
+ {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"},
+ {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"},
+ {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"},
+ {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"},
+ {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"},
]
[package.dependencies]
@@ -1416,18 +1446,18 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pylint"
-version = "4.0.2"
+version = "4.0.4"
description = "python code static checker"
optional = false
python-versions = ">=3.10.0"
groups = ["lint-and-format"]
files = [
- {file = "pylint-4.0.2-py3-none-any.whl", hash = "sha256:9627ccd129893fb8ee8e8010261cb13485daca83e61a6f854a85528ee579502d"},
- {file = "pylint-4.0.2.tar.gz", hash = "sha256:9c22dfa52781d3b79ce86ab2463940f874921a3e5707bcfc98dd0c019945014e"},
+ {file = "pylint-4.0.4-py3-none-any.whl", hash = "sha256:63e06a37d5922555ee2c20963eb42559918c20bd2b21244e4ef426e7c43b92e0"},
+ {file = "pylint-4.0.4.tar.gz", hash = "sha256:d9b71674e19b1c36d79265b5887bf8e55278cbe236c9e95d22dc82cf044fdbd2"},
]
[package.dependencies]
-astroid = ">=4.0.1,<=4.1.dev0"
+astroid = ">=4.0.2,<=4.1.dev0"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
dill = [
{version = ">=0.2", markers = "python_version < \"3.11\""},
@@ -1465,21 +1495,6 @@ all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"]
dev = ["twine (>=3.4.1)"]
nodejs = ["nodejs-wheel-binaries"]
-[[package]]
-name = "pytokens"
-version = "0.2.0"
-description = "A Fast, spec compliant Python 3.13+ tokenizer that runs on older Pythons."
-optional = false
-python-versions = ">=3.8"
-groups = ["main"]
-files = [
- {file = "pytokens-0.2.0-py3-none-any.whl", hash = "sha256:74d4b318c67f4295c13782ddd9abcb7e297ec5630ad060eb90abf7ebbefe59f8"},
- {file = "pytokens-0.2.0.tar.gz", hash = "sha256:532d6421364e5869ea57a9523bf385f02586d4662acbcc0342afd69511b4dd43"},
-]
-
-[package.extras]
-dev = ["black", "build", "mypy", "pytest", "pytest-cov", "setuptools", "tox", "twine", "wheel"]
-
[[package]]
name = "pytz"
version = "2025.2"
@@ -1650,14 +1665,14 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"]
[[package]]
name = "rich-click"
-version = "1.9.4"
+version = "1.9.5"
description = "Format click help output nicely with rich"
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "rich_click-1.9.4-py3-none-any.whl", hash = "sha256:d70f39938bcecaf5543e8750828cbea94ef51853f7d0e174cda1e10543767389"},
- {file = "rich_click-1.9.4.tar.gz", hash = "sha256:af73dc68e85f3bebb80ce302a642b9fe3b65f3df0ceb42eb9a27c467c1b678c8"},
+ {file = "rich_click-1.9.5-py3-none-any.whl", hash = "sha256:9b195721a773b1acf0e16ff9ec68cef1e7d237e53471e6e3f7ade462f86c403a"},
+ {file = "rich_click-1.9.5.tar.gz", hash = "sha256:48120531493f1533828da80e13e839d471979ec8d7d0ca7b35f86a1379cc74b6"},
]
[package.dependencies]
@@ -1672,62 +1687,62 @@ docs = ["markdown-include (>=0.8.1)", "mike (>=2.1.3)", "mkdocs-github-admonitio
[[package]]
name = "robotcode"
-version = "2.0.1"
+version = "2.1.0"
description = "Command line interface for RobotCode"
optional = false
python-versions = ">=3.10"
groups = ["dev", "type-checking"]
files = [
- {file = "robotcode-2.0.1-py3-none-any.whl", hash = "sha256:4259fc1a3b261c7d01194c019fd7c9cd800c93a0d18a5d3b45d5bd21293ac913"},
- {file = "robotcode-2.0.1.tar.gz", hash = "sha256:2fe0509d91e2f7e351c24bc83cb493ef443ca12c32b0b0a1499309015dd544d1"},
+ {file = "robotcode-2.1.0-py3-none-any.whl", hash = "sha256:5e10b76b445b9818b92e5e242b58e1fbdfdc2c3c0ebf9e692b9e999211d66ba7"},
+ {file = "robotcode-2.1.0.tar.gz", hash = "sha256:c476dfac87b11ecc9223bd6a14790c7211bef7896ef62125bc8ff58ebd2b208f"},
]
[package.dependencies]
-robotcode-core = "2.0.1"
-robotcode-plugin = "2.0.1"
-robotcode-robot = "2.0.1"
+robotcode-core = "*"
+robotcode-plugin = "*"
+robotcode-robot = "*"
[package.extras]
-all = ["docutils", "pyyaml (>=5.4)", "rich", "robotcode-analyze (==2.0.1)", "robotcode-debugger (==2.0.1)", "robotcode-language-server (==2.0.1)", "robotcode-repl (==2.0.1)", "robotcode-repl-server (==2.0.1)", "robotcode-runner (==2.0.1)", "robotframework-robocop (>=2.0.0)"]
-analyze = ["robotcode-analyze (==2.0.1)"]
+all = ["docutils", "pyyaml (>=5.4)", "rich", "robotcode-analyze", "robotcode-debugger", "robotcode-language-server", "robotcode-repl", "robotcode-repl-server", "robotcode-runner", "robotframework-robocop (>=6.0.0)"]
+analyze = ["robotcode-analyze (==2.1.0)"]
colored = ["rich"]
-debugger = ["robotcode-debugger (==2.0.1)"]
-languageserver = ["robotcode-language-server (==2.0.1)"]
+debugger = ["robotcode-debugger (==2.1.0)"]
+languageserver = ["robotcode-language-server (==2.1.0)"]
lint = ["robotframework-robocop (>=2.0.0)"]
-repl = ["robotcode-repl (==2.0.1)"]
-replserver = ["robotcode-repl-server (==2.0.1)"]
+repl = ["robotcode-repl (==2.1.0)"]
+replserver = ["robotcode-repl-server (==2.1.0)"]
rest = ["docutils"]
-runner = ["robotcode-runner (==2.0.1)"]
+runner = ["robotcode-runner (==2.1.0)"]
yaml = ["pyyaml (>=5.4)"]
[[package]]
name = "robotcode-analyze"
-version = "2.0.1"
+version = "2.1.0"
description = "RobotCode analyze plugin for Robot Framework"
optional = false
python-versions = ">=3.10"
groups = ["type-checking"]
files = [
- {file = "robotcode_analyze-2.0.1-py3-none-any.whl", hash = "sha256:b6589fb93b90d82b8506301833157bd243bded858cf2d890b78387bc9ca9d5bf"},
- {file = "robotcode_analyze-2.0.1.tar.gz", hash = "sha256:4e57805e8ee79f8fb5c210c15f39e817ed109200e53d26c9fa81dd9474a87eab"},
+ {file = "robotcode_analyze-2.1.0-py3-none-any.whl", hash = "sha256:08eba0935c01c8ccb74b193043463c114c392363dd2c37dbf79f0fc44909ffe6"},
+ {file = "robotcode_analyze-2.1.0.tar.gz", hash = "sha256:9d6e29c67302f38a92963c85d9523f72bbd1cae6494658cee283c02c4095e333"},
]
[package.dependencies]
-robotcode = "2.0.1"
-robotcode-plugin = "2.0.1"
-robotcode-robot = "2.0.1"
-robotframework = ">=4.1.0"
+robotcode = "*"
+robotcode-plugin = "*"
+robotcode-robot = "*"
+robotframework = ">=5.0.0"
[[package]]
name = "robotcode-core"
-version = "2.0.1"
+version = "2.1.0"
description = "Some core classes for RobotCode"
optional = false
python-versions = ">=3.10"
groups = ["dev", "type-checking"]
files = [
- {file = "robotcode_core-2.0.1-py3-none-any.whl", hash = "sha256:0f77be39d42ad4e331e5b7e19809fc631fb046b1424426d3f582ac2a83eb127a"},
- {file = "robotcode_core-2.0.1.tar.gz", hash = "sha256:0e5240064f057ff9e64641e896ed16c23c134ecdf9b5116a4c574a5477e4e679"},
+ {file = "robotcode_core-2.1.0-py3-none-any.whl", hash = "sha256:fc6603553adef020fe43db962dc5145d60aee3983ede9b587cf9b2ce54ac21b9"},
+ {file = "robotcode_core-2.1.0.tar.gz", hash = "sha256:e8c100e037d72dde2da737f6e4ab0b0084e6db4205fe4dd852d63213dd319ff3"},
]
[package.dependencies]
@@ -1735,29 +1750,29 @@ typing-extensions = ">=4.4.0"
[[package]]
name = "robotcode-modifiers"
-version = "2.0.1"
+version = "2.1.0"
description = "Some Robot Framework Modifiers for RobotCode"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "robotcode_modifiers-2.0.1-py3-none-any.whl", hash = "sha256:827dc9c1ed4f63b6ebce6be67a105d2030cc18266dd4cf8de14f5411ea968678"},
- {file = "robotcode_modifiers-2.0.1.tar.gz", hash = "sha256:ce6159925d86360a1f205d4a45a57c7facf7fd3b3c95a1a966805a66b5dd7fab"},
+ {file = "robotcode_modifiers-2.1.0-py3-none-any.whl", hash = "sha256:2b941b7c21d96c0cbe060f3e88dda57ab72ddbb88b1951acf6d2fbfbb4b0b078"},
+ {file = "robotcode_modifiers-2.1.0.tar.gz", hash = "sha256:a31a6be68ab6c6dd02f7a3c6a86dc1c4381c9cb9e8bc40146a562f59187f2c7e"},
]
[package.dependencies]
-robotframework = ">=4.1.0"
+robotframework = ">=5.0.0"
[[package]]
name = "robotcode-plugin"
-version = "2.0.1"
+version = "2.1.0"
description = "Some classes for RobotCode plugin management"
optional = false
python-versions = ">=3.10"
groups = ["dev", "type-checking"]
files = [
- {file = "robotcode_plugin-2.0.1-py3-none-any.whl", hash = "sha256:85e239edfd8c4b6e28af2ee9139958eaef9f09e0aef395f2ed2bd2d5593db4f6"},
- {file = "robotcode_plugin-2.0.1.tar.gz", hash = "sha256:ceb89b663f8e017b1b20bf770113ab51074cf06516187f695e9917e8a84279b4"},
+ {file = "robotcode_plugin-2.1.0-py3-none-any.whl", hash = "sha256:140323c767ef96c34527807176eff0dd67e8b1680fb8086886596cab5a17f0fe"},
+ {file = "robotcode_plugin-2.1.0.tar.gz", hash = "sha256:8bfc2eb78ec72c78f0bad7efa9eebb7345a121abc75a24882d1f2af80f14b838"},
]
[package.dependencies]
@@ -1768,51 +1783,51 @@ tomli-w = ">=1.0.0"
[[package]]
name = "robotcode-robot"
-version = "2.0.1"
+version = "2.1.0"
description = "Support classes for RobotCode for handling Robot Framework projects."
optional = false
python-versions = ">=3.10"
groups = ["dev", "type-checking"]
files = [
- {file = "robotcode_robot-2.0.1-py3-none-any.whl", hash = "sha256:628cafed3525a28928f95ae7f4a79dedd3983b042b443a8e0d10380cc8617b85"},
- {file = "robotcode_robot-2.0.1.tar.gz", hash = "sha256:bb7bba064bd4dbda240975fb9093388926daef5cc7faaa14ed366f89fc38f543"},
+ {file = "robotcode_robot-2.1.0-py3-none-any.whl", hash = "sha256:5d07364576119fd5c580ddf0ee12eb42b661e9ecaf07c9b030ae44de38b01c36"},
+ {file = "robotcode_robot-2.1.0.tar.gz", hash = "sha256:048389b8f81677b5cb9356d6fa1ea68f4254e984efc17c8409737d6a53d2cb62"},
]
[package.dependencies]
platformdirs = ">=4.3"
-robotcode-core = "2.0.1"
-robotframework = ">=4.1.0"
+robotcode-core = "*"
+robotframework = ">=5.0.0"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
[[package]]
name = "robotcode-runner"
-version = "2.0.1"
+version = "2.1.0"
description = "RobotCode runner for Robot Framework"
optional = false
python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "robotcode_runner-2.0.1-py3-none-any.whl", hash = "sha256:7df0b67fa647fa75bea651c7caf1be6128d54836e5f7cc12d3117f77bd10d644"},
- {file = "robotcode_runner-2.0.1.tar.gz", hash = "sha256:aa350a2b0fe19d32c6efde223e4967d206f63ba085b6c444e914c7855dd15379"},
+ {file = "robotcode_runner-2.1.0-py3-none-any.whl", hash = "sha256:02287c14e15d4e8b38409522eabf0c67dcc004b83aac7d8e8f55125eec78297c"},
+ {file = "robotcode_runner-2.1.0.tar.gz", hash = "sha256:c22bbb1ec842d741673332e87777c2458975324d3960eb86df9cb7a498952f72"},
]
[package.dependencies]
-robotcode = "2.0.1"
-robotcode-modifiers = "2.0.1"
-robotcode-plugin = "2.0.1"
-robotcode-robot = "2.0.1"
-robotframework = ">=4.1.0"
+robotcode = "*"
+robotcode-modifiers = "*"
+robotcode-plugin = "*"
+robotcode-robot = "*"
+robotframework = ">=5.0.0"
[[package]]
name = "robotframework"
-version = "7.3.2"
+version = "7.4.1"
description = "Generic automation framework for acceptance testing and robotic process automation (RPA)"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev", "lint-and-format", "type-checking"]
files = [
- {file = "robotframework-7.3.2-py3-none-any.whl", hash = "sha256:14ef2afa905285cc073df6ce06d0cd3af4a113df6f815532718079e00c98cca4"},
- {file = "robotframework-7.3.2.tar.gz", hash = "sha256:3bb3e299831ecb1664f3d5082f6ff9f08ba82d61a745bef2227328ef3049e93a"},
+ {file = "robotframework-7.4.1-py3-none-any.whl", hash = "sha256:3f75cae8a24797f1953efdb51399569767d742bb31f549ee9856db5545062728"},
+ {file = "robotframework-7.4.1.tar.gz", hash = "sha256:d645487248a86db1e1a865ce792502792edf5342873f4e4f35d333219cd246c5"},
]
[[package]]
@@ -1837,14 +1852,14 @@ xls = ["openpyxl", "pandas", "xlrd (>=1.2.0)"]
[[package]]
name = "robotframework-robocop"
-version = "6.9.2"
+version = "7.0.0"
description = "Static code analysis tool (linter) and code formatter for Robot Framework"
optional = false
python-versions = ">=3.9"
groups = ["lint-and-format"]
files = [
- {file = "robotframework_robocop-6.9.2-py3-none-any.whl", hash = "sha256:1b6111c614cce67af33998aa35cac60ccc8a1e495b0be44b6b8892a7cdcc7cf9"},
- {file = "robotframework_robocop-6.9.2.tar.gz", hash = "sha256:461b1ae8ad9a43ae1a29ba343ec9b626c65cd8615938e94b76c3f32c0eee39f6"},
+ {file = "robotframework_robocop-7.0.0-py3-none-any.whl", hash = "sha256:966cc703183fc562c6f8b75a3342cd005bd0a54da0e4172fab8ee3e3f70d34c2"},
+ {file = "robotframework_robocop-7.0.0.tar.gz", hash = "sha256:1533c613c69f9e264b051da473aab018fa03f509ad51bd441b54ca5cd7a10fea"},
]
[package.dependencies]
@@ -1875,127 +1890,127 @@ robotframework = ">=3.2"
[[package]]
name = "rpds-py"
-version = "0.28.0"
+version = "0.30.0"
description = "Python bindings to Rust's persistent data structures (rpds)"
optional = false
python-versions = ">=3.10"
groups = ["main"]
files = [
- {file = "rpds_py-0.28.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7b6013db815417eeb56b2d9d7324e64fcd4fa289caeee6e7a78b2e11fc9b438a"},
- {file = "rpds_py-0.28.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a4c6b05c685c0c03f80dabaeb73e74218c49deea965ca63f76a752807397207"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4794c6c3fbe8f9ac87699b131a1f26e7b4abcf6d828da46a3a52648c7930eba"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e8456b6ee5527112ff2354dd9087b030e3429e43a74f480d4a5ca79d269fd85"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:beb880a9ca0a117415f241f66d56025c02037f7c4efc6fe59b5b8454f1eaa50d"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6897bebb118c44b38c9cb62a178e09f1593c949391b9a1a6fe777ccab5934ee7"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b553dd06e875249fd43efd727785efb57a53180e0fde321468222eabbeaafa"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:f0b2044fdddeea5b05df832e50d2a06fe61023acb44d76978e1b060206a8a476"},
- {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05cf1e74900e8da73fa08cc76c74a03345e5a3e37691d07cfe2092d7d8e27b04"},
- {file = "rpds_py-0.28.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:efd489fec7c311dae25e94fe7eeda4b3d06be71c68f2cf2e8ef990ffcd2cd7e8"},
- {file = "rpds_py-0.28.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ada7754a10faacd4f26067e62de52d6af93b6d9542f0df73c57b9771eb3ba9c4"},
- {file = "rpds_py-0.28.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c2a34fd26588949e1e7977cfcbb17a9a42c948c100cab890c6d8d823f0586457"},
- {file = "rpds_py-0.28.0-cp310-cp310-win32.whl", hash = "sha256:f9174471d6920cbc5e82a7822de8dfd4dcea86eb828b04fc8c6519a77b0ee51e"},
- {file = "rpds_py-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:6e32dd207e2c4f8475257a3540ab8a93eff997abfa0a3fdb287cae0d6cd874b8"},
- {file = "rpds_py-0.28.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:03065002fd2e287725d95fbc69688e0c6daf6c6314ba38bdbaa3895418e09296"},
- {file = "rpds_py-0.28.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28ea02215f262b6d078daec0b45344c89e161eab9526b0d898221d96fdda5f27"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25dbade8fbf30bcc551cb352376c0ad64b067e4fc56f90e22ba70c3ce205988c"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c03002f54cc855860bfdc3442928ffdca9081e73b5b382ed0b9e8efe6e5e205"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9699fa7990368b22032baf2b2dce1f634388e4ffc03dfefaaac79f4695edc95"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9b06fe1a75e05e0713f06ea0c89ecb6452210fd60e2f1b6ddc1067b990e08d9"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9f83e7b326a3f9ec3ef84cda98fb0a74c7159f33e692032233046e7fd15da2"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:0d3259ea9ad8743a75a43eb7819324cdab393263c91be86e2d1901ee65c314e0"},
- {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a7548b345f66f6695943b4ef6afe33ccd3f1b638bd9afd0f730dd255c249c9e"},
- {file = "rpds_py-0.28.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9a40040aa388b037eb39416710fbcce9443498d2eaab0b9b45ae988b53f5c67"},
- {file = "rpds_py-0.28.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f60c7ea34e78c199acd0d3cda37a99be2c861dd2b8cf67399784f70c9f8e57d"},
- {file = "rpds_py-0.28.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1571ae4292649100d743b26d5f9c63503bb1fedf538a8f29a98dce2d5ba6b4e6"},
- {file = "rpds_py-0.28.0-cp311-cp311-win32.whl", hash = "sha256:5cfa9af45e7c1140af7321fa0bef25b386ee9faa8928c80dc3a5360971a29e8c"},
- {file = "rpds_py-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd8d86b5d29d1b74100982424ba53e56033dc47720a6de9ba0259cf81d7cecaa"},
- {file = "rpds_py-0.28.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e27d3a5709cc2b3e013bf93679a849213c79ae0573f9b894b284b55e729e120"},
- {file = "rpds_py-0.28.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6b4f28583a4f247ff60cd7bdda83db8c3f5b05a7a82ff20dd4b078571747708f"},
- {file = "rpds_py-0.28.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d678e91b610c29c4b3d52a2c148b641df2b4676ffe47c59f6388d58b99cdc424"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e819e0e37a44a78e1383bf1970076e2ccc4dc8c2bbaa2f9bd1dc987e9afff628"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5ee514e0f0523db5d3fb171f397c54875dbbd69760a414dccf9d4d7ad628b5bd"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3fa06d27fdcee47f07a39e02862da0100cb4982508f5ead53ec533cd5fe55e"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46959ef2e64f9e4a41fc89aa20dbca2b85531f9a72c21099a3360f35d10b0d5a"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8455933b4bcd6e83fde3fefc987a023389c4b13f9a58c8d23e4b3f6d13f78c84"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ad50614a02c8c2962feebe6012b52f9802deec4263946cddea37aaf28dd25a66"},
- {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5deca01b271492553fdb6c7fd974659dce736a15bae5dad7ab8b93555bceb28"},
- {file = "rpds_py-0.28.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:735f8495a13159ce6a0d533f01e8674cec0c57038c920495f87dcb20b3ddb48a"},
- {file = "rpds_py-0.28.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:961ca621ff10d198bbe6ba4957decca61aa2a0c56695384c1d6b79bf61436df5"},
- {file = "rpds_py-0.28.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2374e16cc9131022e7d9a8f8d65d261d9ba55048c78f3b6e017971a4f5e6353c"},
- {file = "rpds_py-0.28.0-cp312-cp312-win32.whl", hash = "sha256:d15431e334fba488b081d47f30f091e5d03c18527c325386091f31718952fe08"},
- {file = "rpds_py-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:a410542d61fc54710f750d3764380b53bf09e8c4edbf2f9141a82aa774a04f7c"},
- {file = "rpds_py-0.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:1f0cfd1c69e2d14f8c892b893997fa9a60d890a0c8a603e88dca4955f26d1edd"},
- {file = "rpds_py-0.28.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b"},
- {file = "rpds_py-0.28.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d"},
- {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb"},
- {file = "rpds_py-0.28.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41"},
- {file = "rpds_py-0.28.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7"},
- {file = "rpds_py-0.28.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9"},
- {file = "rpds_py-0.28.0-cp313-cp313-win32.whl", hash = "sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5"},
- {file = "rpds_py-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e"},
- {file = "rpds_py-0.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1"},
- {file = "rpds_py-0.28.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c"},
- {file = "rpds_py-0.28.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259"},
- {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a"},
- {file = "rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f"},
- {file = "rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37"},
- {file = "rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712"},
- {file = "rpds_py-0.28.0-cp313-cp313t-win32.whl", hash = "sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342"},
- {file = "rpds_py-0.28.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907"},
- {file = "rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472"},
- {file = "rpds_py-0.28.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f274f56a926ba2dc02976ca5b11c32855cbd5925534e57cfe1fda64e04d1add2"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe0438ac4a29a520ea94c8c7f1754cdd8feb1bc490dfda1bfd990072363d527"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a358a32dd3ae50e933347889b6af9a1bdf207ba5d1a3f34e1a38cd3540e6733"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e80848a71c78aa328fefaba9c244d588a342c8e03bda518447b624ea64d1ff56"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f586db2e209d54fe177e58e0bc4946bea5fb0102f150b1b2f13de03e1f0976f8"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ae8ee156d6b586e4292491e885d41483136ab994e719a13458055bec14cf370"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a805e9b3973f7e27f7cab63a6b4f61d90f2e5557cff73b6e97cd5b8540276d3d"},
- {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d3fd16b6dc89c73a4da0b4ac8b12a7ecc75b2864b95c9e5afed8003cb50a728"},
- {file = "rpds_py-0.28.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6796079e5d24fdaba6d49bda28e2c47347e89834678f2bc2c1b4fc1489c0fb01"},
- {file = "rpds_py-0.28.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:76500820c2af232435cbe215e3324c75b950a027134e044423f59f5b9a1ba515"},
- {file = "rpds_py-0.28.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bbdc5640900a7dbf9dd707fe6388972f5bbd883633eb68b76591044cfe346f7e"},
- {file = "rpds_py-0.28.0-cp314-cp314-win32.whl", hash = "sha256:adc8aa88486857d2b35d75f0640b949759f79dc105f50aa2c27816b2e0dd749f"},
- {file = "rpds_py-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:66e6fa8e075b58946e76a78e69e1a124a21d9a48a5b4766d15ba5b06869d1fa1"},
- {file = "rpds_py-0.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6fe887c2c5c59413353b7c0caff25d0e566623501ccfff88957fa438a69377d"},
- {file = "rpds_py-0.28.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7a69df082db13c7070f7b8b1f155fa9e687f1d6aefb7b0e3f7231653b79a067b"},
- {file = "rpds_py-0.28.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b1cde22f2c30ebb049a9e74c5374994157b9b70a16147d332f89c99c5960737a"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5338742f6ba7a51012ea470bd4dc600a8c713c0c72adaa0977a1b1f4327d6592"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1460ebde1bcf6d496d80b191d854adedcc619f84ff17dc1c6d550f58c9efbba"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3eb248f2feba84c692579257a043a7699e28a77d86c77b032c1d9fbb3f0219c"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3bbba5def70b16cd1c1d7255666aad3b290fbf8d0fe7f9f91abafb73611a91"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3114f4db69ac5a1f32e7e4d1cbbe7c8f9cf8217f78e6e002cedf2d54c2a548ed"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4b0cb8a906b1a0196b863d460c0222fb8ad0f34041568da5620f9799b83ccf0b"},
- {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf681ac76a60b667106141e11a92a3330890257e6f559ca995fbb5265160b56e"},
- {file = "rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e8ee6413cfc677ce8898d9cde18cc3a60fc2ba756b0dec5b71eb6eb21c49fa1"},
- {file = "rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3072b16904d0b5572a15eb9d31c1954e0d3227a585fc1351aa9878729099d6c"},
- {file = "rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b670c30fd87a6aec281c3c9896d3bae4b205fd75d79d06dc87c2503717e46092"},
- {file = "rpds_py-0.28.0-cp314-cp314t-win32.whl", hash = "sha256:8014045a15b4d2b3476f0a287fcc93d4f823472d7d1308d47884ecac9e612be3"},
- {file = "rpds_py-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a4e59c90d9c27c561eb3160323634a9ff50b04e4f7820600a2beb0ac90db578"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f5e7101145427087e493b9c9b959da68d357c28c562792300dd21a095118ed16"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:31eb671150b9c62409a888850aaa8e6533635704fe2b78335f9aaf7ff81eec4d"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b55c1f64482f7d8bd39942f376bfdf2f6aec637ee8c805b5041e14eeb771db"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24743a7b372e9a76171f6b69c01aedf927e8ac3e16c474d9fe20d552a8cb45c7"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:389c29045ee8bbb1627ea190b4976a310a295559eaf9f1464a1a6f2bf84dde78"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23690b5827e643150cf7b49569679ec13fe9a610a15949ed48b85eb7f98f34ec"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f0c9266c26580e7243ad0d72fc3e01d6b33866cfab5084a6da7576bcf1c4f72"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4c6c4db5d73d179746951486df97fd25e92396be07fc29ee8ff9a8f5afbdfb27"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3b695a8fa799dd2cfdb4804b37096c5f6dba1ac7f48a7fbf6d0485bcd060316"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6aa1bfce3f83baf00d9c5fcdbba93a3ab79958b4c7d7d1f55e7fe68c20e63912"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b0f9dceb221792b3ee6acb5438eb1f02b0cb2c247796a72b016dcc92c6de829"},
- {file = "rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5d0145edba8abd3db0ab22b5300c99dc152f5c9021fab861be0f0544dc3cbc5f"},
- {file = "rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea"},
+ {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"},
+ {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"},
+ {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"},
+ {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"},
+ {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"},
+ {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"},
+ {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"},
+ {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"},
+ {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"},
+ {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"},
+ {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"},
+ {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"},
+ {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"},
+ {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"},
+ {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"},
+ {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"},
+ {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"},
+ {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"},
+ {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"},
+ {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"},
+ {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"},
+ {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"},
+ {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"},
+ {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"},
+ {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"},
+ {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"},
+ {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"},
+ {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"},
+ {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"},
+ {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"},
+ {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"},
+ {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"},
+ {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"},
+ {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"},
+ {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"},
+ {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"},
+ {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"},
+ {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"},
+ {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"},
+ {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"},
+ {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"},
+ {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"},
+ {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"},
+ {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"},
+ {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"},
+ {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"},
+ {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"},
+ {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"},
]
[[package]]
@@ -2012,18 +2027,18 @@ files = [
[[package]]
name = "ruamel-yaml"
-version = "0.18.16"
+version = "0.18.17"
description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order"
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "ruamel.yaml-0.18.16-py3-none-any.whl", hash = "sha256:048f26d64245bae57a4f9ef6feb5b552a386830ef7a826f235ffb804c59efbba"},
- {file = "ruamel.yaml-0.18.16.tar.gz", hash = "sha256:a6e587512f3c998b2225d68aa1f35111c29fad14aed561a26e73fab729ec5e5a"},
+ {file = "ruamel_yaml-0.18.17-py3-none-any.whl", hash = "sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d"},
+ {file = "ruamel_yaml-0.18.17.tar.gz", hash = "sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c"},
]
[package.dependencies]
-"ruamel.yaml.clib" = {version = ">=0.2.7", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.14\""}
+"ruamel.yaml.clib" = {version = ">=0.2.15", markers = "platform_python_implementation == \"CPython\" and python_version < \"3.15\""}
[package.extras]
docs = ["mercurial (>5.7)", "ryd"]
@@ -2031,97 +2046,103 @@ jinja2 = ["ruamel.yaml.jinja2 (>=0.2)"]
[[package]]
name = "ruamel-yaml-clib"
-version = "0.2.14"
+version = "0.2.15"
description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml"
optional = false
python-versions = ">=3.9"
groups = ["main"]
-markers = "python_version < \"3.14\" and platform_python_implementation == \"CPython\""
-files = [
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f8b2acb0ffdd2ce8208accbec2dca4a06937d556fdcaefd6473ba1b5daa7e3c4"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:aef953f3b8bd0b50bd52a2e52fb54a6a2171a1889d8dea4a5959d46c6624c451"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a0ac90efbc7a77b0d796c03c8cc4e62fd710b3f1e4c32947713ef2ef52e09543"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9bf6b699223afe6c7fe9f2ef76e0bfa6dd892c21e94ce8c957478987ade76cd8"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d73a0187718f6eec5b2f729b0f98e4603f7bd9c48aa65d01227d1a5dcdfbe9e8"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:81f6d3b19bc703679a5705c6a16dabdc79823c71d791d73c65949be7f3012c02"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b28caeaf3e670c08cb7e8de221266df8494c169bd6ed8875493fab45be9607a4"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:94f3efb718f8f49b031f2071ec7a27dd20cbfe511b4dfd54ecee54c956da2b31"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-win32.whl", hash = "sha256:27c070cf3888e90d992be75dd47292ff9aa17dafd36492812a6a304a1aedc182"},
- {file = "ruamel.yaml.clib-0.2.14-cp310-cp310-win_amd64.whl", hash = "sha256:4f4a150a737fccae13fb51234d41304ff2222e3b7d4c8e9428ed1a6ab48389b8"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bae1a073ca4244620425cd3d3aa9746bde590992b98ee8c7c8be8c597ca0d4e"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:0a54e5e40a7a691a426c2703b09b0d61a14294d25cfacc00631aa6f9c964df0d"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:10d9595b6a19778f3269399eff6bab642608e5966183abc2adbe558a42d4efc9"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dba72975485f2b87b786075e18a6e5d07dc2b4d8973beb2732b9b2816f1bad70"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29757bdb7c142f9595cc1b62ec49a3d1c83fab9cef92db52b0ccebaad4eafb98"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:557df28dbccf79b152fe2d1b935f6063d9cc431199ea2b0e84892f35c03bb0ee"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:26a8de280ab0d22b6e3ec745b4a5a07151a0f74aad92dd76ab9c8d8d7087720d"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e501c096aa3889133d674605ebd018471bc404a59cbc17da3c5924421c54d97c"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-win32.whl", hash = "sha256:915748cfc25b8cfd81b14d00f4bfdb2ab227a30d6d43459034533f4d1c207a2a"},
- {file = "ruamel.yaml.clib-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:4ccba93c1e5a40af45b2f08e4591969fa4697eae951c708f3f83dcbf9f6c6bb1"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6aeadc170090ff1889f0d2c3057557f9cd71f975f17535c26a5d37af98f19c27"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5e56ac47260c0eed992789fa0b8efe43404a9adb608608631a948cee4fc2b052"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:a911aa73588d9a8b08d662b9484bc0567949529824a55d3885b77e8dd62a127a"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a05ba88adf3d7189a974b2de7a9d56731548d35dc0a822ec3dc669caa7019b29"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb04c5650de6668b853623eceadcdb1a9f2fee381f5d7b6bc842ee7c239eeec4"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df3ec9959241d07bc261f4983d25a1205ff37703faf42b474f15d54d88b4f8c9"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fbc08c02e9b147a11dfcaa1ac8a83168b699863493e183f7c0c8b12850b7d259"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c099cafc1834d3c5dac305865d04235f7c21c167c8dd31ebc3d6bbc357e2f023"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-win32.whl", hash = "sha256:b5b0f7e294700b615a3bcf6d28b26e6da94e8eba63b079f4ec92e9ba6c0d6b54"},
- {file = "ruamel.yaml.clib-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:a37f40a859b503304dd740686359fcf541d6fb3ff7fc10f539af7f7150917c68"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7e4f9da7e7549946e02a6122dcad00b7c1168513acb1f8a726b1aaf504a99d32"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:dd7546c851e59c06197a7c651335755e74aa383a835878ca86d2c650c07a2f85"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:1c1acc3a0209ea9042cc3cfc0790edd2eddd431a2ec3f8283d081e4d5018571e"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2070bf0ad1540d5c77a664de07ebcc45eebd1ddcab71a7a06f26936920692beb"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bd8fe07f49c170e09d76773fb86ad9135e0beee44f36e1576a201b0676d3d1d"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ff86876889ea478b1381089e55cf9e345707b312beda4986f823e1d95e8c0f59"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1f118b707eece8cf84ecbc3e3ec94d9db879d85ed608f95870d39b2d2efa5dca"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b30110b29484adc597df6bd92a37b90e63a8c152ca8136aad100a02f8ba6d1b6"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-win32.whl", hash = "sha256:f4e97a1cf0b7a30af9e1d9dad10a5671157b9acee790d9e26996391f49b965a2"},
- {file = "ruamel.yaml.clib-0.2.14-cp313-cp313-win_amd64.whl", hash = "sha256:090782b5fb9d98df96509eecdbcaffd037d47389a89492320280d52f91330d78"},
- {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7df6f6e9d0e33c7b1d435defb185095386c469109de723d514142632a7b9d07f"},
- {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:70eda7703b8126f5e52fcf276e6c0f40b0d314674f896fc58c47b0aef2b9ae83"},
- {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a0cb71ccc6ef9ce36eecb6272c81afdc2f565950cdcec33ae8e6cd8f7fc86f27"},
- {file = "ruamel.yaml.clib-0.2.14-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e7cb9ad1d525d40f7d87b6df7c0ff916a66bc52cb61b66ac1b2a16d0c1b07640"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:18c041b28f3456ddef1f1951d4492dbebe0f8114157c1b3c981a4611c2020792"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:d8354515ab62f95a07deaf7f845886cc50e2f345ceab240a3d2d09a9f7d77853"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:275f938692013a3883edbd848edde6d9f26825d65c9a2eb1db8baa1adc96a05d"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16a60d69f4057ad9a92f3444e2367c08490daed6428291aa16cefb445c29b0e9"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ac5ff9425d8acb8f59ac5b96bcb7fd3d272dc92d96a7c730025928ffcc88a7a"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e1d1735d97fd8a48473af048739379975651fab186f8a25a9f683534e6904179"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:83bbd8354f6abb3fdfb922d1ed47ad8d1db3ea72b0523dac8d07cdacfe1c0fcf"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:808c7190a0fe7ae7014c42f73897cf8e9ef14ff3aa533450e51b1e72ec5239ad"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-win32.whl", hash = "sha256:6d5472f63a31b042aadf5ed28dd3ef0523da49ac17f0463e10fda9c4a2773352"},
- {file = "ruamel.yaml.clib-0.2.14-cp39-cp39-win_amd64.whl", hash = "sha256:8dd3c2cc49caa7a8d64b67146462aed6723a0495e44bf0aa0a2e94beaa8432f6"},
- {file = "ruamel.yaml.clib-0.2.14.tar.gz", hash = "sha256:803f5044b13602d58ea378576dd75aa759f52116a0232608e8fdada4da33752e"},
+markers = "platform_python_implementation == \"CPython\" and python_version < \"3.15\""
+files = [
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:88eea8baf72f0ccf232c22124d122a7f26e8a24110a0273d9bcddcb0f7e1fa03"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b6f7d74d094d1f3a4e157278da97752f16ee230080ae331fcc219056ca54f77"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4be366220090d7c3424ac2b71c90d1044ea34fca8c0b88f250064fd06087e614"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f66f600833af58bea694d5892453f2270695b92200280ee8c625ec5a477eed3"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da3d6adadcf55a93c214d23941aef4abfd45652110aed6580e814152f385b862"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e9fde97ecb7bb9c41261c2ce0da10323e9227555c674989f8d9eb7572fc2098d"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:05c70f7f86be6f7bee53794d80050a28ae7e13e4a0087c1839dcdefd68eb36b6"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f1d38cbe622039d111b69e9ca945e7e3efebb30ba998867908773183357f3ed"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-win32.whl", hash = "sha256:fe239bdfdae2302e93bd6e8264bd9b71290218fff7084a9db250b55caaccf43f"},
+ {file = "ruamel_yaml_clib-0.2.15-cp310-cp310-win_amd64.whl", hash = "sha256:468858e5cbde0198337e6a2a78eda8c3fb148bdf4c6498eaf4bc9ba3f8e780bd"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c"},
+ {file = "ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144"},
+ {file = "ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dcec721fddbb62e60c2801ba08c87010bd6b700054a09998c4d09c08147b8fb"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:65f48245279f9bb301d1276f9679b82e4c080a1ae25e679f682ac62446fac471"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:46895c17ead5e22bea5e576f1db7e41cb273e8d062c04a6a49013d9f60996c25"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3eb199178b08956e5be6288ee0b05b2fb0b5c1f309725ad25d9c6ea7e27f962a"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d1032919280ebc04a80e4fb1e93f7a738129857eaec9448310e638c8bccefcf"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab0df0648d86a7ecbd9c632e8f8d6b21bb21b5fc9d9e095c796cacf32a728d2d"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:331fb180858dd8534f0e61aa243b944f25e73a4dae9962bd44c46d1761126bbf"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd4c928ddf6bce586285daa6d90680b9c291cfd045fc40aad34e445d57b1bf51"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-win32.whl", hash = "sha256:bf0846d629e160223805db9fe8cc7aec16aaa11a07310c50c8c7164efa440aec"},
+ {file = "ruamel_yaml_clib-0.2.15-cp313-cp313-win_amd64.whl", hash = "sha256:45702dfbea1420ba3450bb3dd9a80b33f0badd57539c6aac09f42584303e0db6"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:753faf20b3a5906faf1fc50e4ddb8c074cb9b251e00b14c18b28492f933ac8ef"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:480894aee0b29752560a9de46c0e5f84a82602f2bc5c6cde8db9a345319acfdf"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4d3b58ab2454b4747442ac76fab66739c72b1e2bb9bd173d7694b9f9dbc9c000"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bfd309b316228acecfa30670c3887dcedf9b7a44ea39e2101e75d2654522acd4"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2812ff359ec1f30129b62372e5f22a52936fac13d5d21e70373dbca5d64bb97c"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7e74ea87307303ba91073b63e67f2c667e93f05a8c63079ee5b7a5c8d0d7b043"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:713cd68af9dfbe0bb588e144a61aad8dcc00ef92a82d2e87183ca662d242f524"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:542d77b72786a35563f97069b9379ce762944e67055bea293480f7734b2c7e5e"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-win32.whl", hash = "sha256:424ead8cef3939d690c4b5c85ef5b52155a231ff8b252961b6516ed7cf05f6aa"},
+ {file = "ruamel_yaml_clib-0.2.15-cp314-cp314-win_amd64.whl", hash = "sha256:ac9b8d5fa4bb7fd2917ab5027f60d4234345fd366fe39aa711d5dca090aa1467"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:923816815974425fbb1f1bf57e85eca6e14d8adc313c66db21c094927ad01815"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dcc7f3162d3711fd5d52e2267e44636e3e566d1e5675a5f0b30e98f2c4af7974"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5d3c9210219cbc0f22706f19b154c9a798ff65a6beeafbf77fc9c057ec806f7d"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bb7b728fd9f405aa00b4a0b17ba3f3b810d0ccc5f77f7373162e9b5f0ff75d5"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cb75a3c14f1d6c3c2a94631e362802f70e83e20d1f2b2ef3026c05b415c4900"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:badd1d7283f3e5894779a6ea8944cc765138b96804496c91812b2829f70e18a7"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0ba6604bbc3dfcef844631932d06a1a4dcac3fee904efccf582261948431628a"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a8220fd4c6f98485e97aea65e1df76d4fed1678ede1fe1d0eed2957230d287c4"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-win32.whl", hash = "sha256:04d21dc9c57d9608225da28285900762befbb0165ae48482c15d8d4989d4af14"},
+ {file = "ruamel_yaml_clib-0.2.15-cp39-cp39-win_amd64.whl", hash = "sha256:27dc656e84396e6d687f97c6e65fb284d100483628f02d95464fd731743a4afe"},
+ {file = "ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600"},
]
[[package]]
name = "ruff"
-version = "0.14.3"
+version = "0.14.10"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
groups = ["lint-and-format"]
files = [
- {file = "ruff-0.14.3-py3-none-linux_armv6l.whl", hash = "sha256:876b21e6c824f519446715c1342b8e60f97f93264012de9d8d10314f8a79c371"},
- {file = "ruff-0.14.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6fd8c79b457bedd2abf2702b9b472147cd860ed7855c73a5247fa55c9117654"},
- {file = "ruff-0.14.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:71ff6edca490c308f083156938c0c1a66907151263c4abdcb588602c6e696a14"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:786ee3ce6139772ff9272aaf43296d975c0217ee1b97538a98171bf0d21f87ed"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cd6291d0061811c52b8e392f946889916757610d45d004e41140d81fb6cd5ddc"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a497ec0c3d2c88561b6d90f9c29f5ae68221ac00d471f306fa21fa4264ce5fcd"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e231e1be58fc568950a04fbe6887c8e4b85310e7889727e2b81db205c45059eb"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:469e35872a09c0e45fecf48dd960bfbce056b5db2d5e6b50eca329b4f853ae20"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d6bc90307c469cb9d28b7cfad90aaa600b10d67c6e22026869f585e1e8a2db0"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2f8a0bbcffcfd895df39c9a4ecd59bb80dca03dc43f7fb63e647ed176b741e"},
- {file = "ruff-0.14.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:678fdd7c7d2d94851597c23ee6336d25f9930b460b55f8598e011b57c74fd8c5"},
- {file = "ruff-0.14.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:1ec1ac071e7e37e0221d2f2dbaf90897a988c531a8592a6a5959f0603a1ecf5e"},
- {file = "ruff-0.14.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afcdc4b5335ef440d19e7df9e8ae2ad9f749352190e96d481dc501b753f0733e"},
- {file = "ruff-0.14.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7bfc42f81862749a7136267a343990f865e71fe2f99cf8d2958f684d23ce3dfa"},
- {file = "ruff-0.14.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a65e448cfd7e9c59fae8cf37f9221585d3354febaad9a07f29158af1528e165f"},
- {file = "ruff-0.14.3-py3-none-win32.whl", hash = "sha256:f3d91857d023ba93e14ed2d462ab62c3428f9bbf2b4fbac50a03ca66d31991f7"},
- {file = "ruff-0.14.3-py3-none-win_amd64.whl", hash = "sha256:d7b7006ac0756306db212fd37116cce2bd307e1e109375e1c6c106002df0ae5f"},
- {file = "ruff-0.14.3-py3-none-win_arm64.whl", hash = "sha256:26eb477ede6d399d898791d01961e16b86f02bc2486d0d1a7a9bb2379d055dc1"},
- {file = "ruff-0.14.3.tar.gz", hash = "sha256:4ff876d2ab2b161b6de0aa1f5bd714e8e9b4033dc122ee006925fbacc4f62153"},
+ {file = "ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49"},
+ {file = "ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f"},
+ {file = "ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f"},
+ {file = "ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f"},
+ {file = "ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d"},
+ {file = "ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405"},
+ {file = "ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60"},
+ {file = "ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830"},
+ {file = "ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6"},
+ {file = "ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154"},
+ {file = "ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6"},
+ {file = "ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4"},
]
[[package]]
@@ -2157,28 +2178,16 @@ files = [
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
]
-[[package]]
-name = "sniffio"
-version = "1.3.1"
-description = "Sniff out which async library your code is running under"
-optional = false
-python-versions = ">=3.7"
-groups = ["dev"]
-files = [
- {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
- {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
-]
-
[[package]]
name = "starlette"
-version = "0.49.3"
+version = "0.50.0"
description = "The little ASGI library that shines."
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f"},
- {file = "starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284"},
+ {file = "starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca"},
+ {file = "starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca"},
]
[package.dependencies]
@@ -2194,8 +2203,8 @@ version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
-groups = ["main", "dev", "lint-and-format", "type-checking"]
-markers = "python_version == \"3.10\""
+groups = ["dev", "lint-and-format", "type-checking"]
+markers = "python_version < \"3.11\""
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
@@ -2257,14 +2266,14 @@ files = [
[[package]]
name = "typer-slim"
-version = "0.20.0"
+version = "0.20.1"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.8"
groups = ["lint-and-format"]
files = [
- {file = "typer_slim-0.20.0-py3-none-any.whl", hash = "sha256:f42a9b7571a12b97dddf364745d29f12221865acef7a2680065f9bb29c7dc89d"},
- {file = "typer_slim-0.20.0.tar.gz", hash = "sha256:9fc6607b3c6c20f5c33ea9590cbeb17848667c51feee27d9e314a579ab07d1a3"},
+ {file = "typer_slim-0.20.1-py3-none-any.whl", hash = "sha256:8e89c5dbaffe87a4f86f4c7a9e2f7059b5b68c66f558f298969d42ce34f10122"},
+ {file = "typer_slim-0.20.1.tar.gz", hash = "sha256:bb9e4f7e6dc31551c8a201383df322b81b0ce37239a5ead302598a2ebb6f7c9c"},
]
[package.dependencies]
@@ -2330,44 +2339,44 @@ typing-extensions = ">=4.12.0"
[[package]]
name = "tzdata"
-version = "2025.2"
+version = "2025.3"
description = "Provider of IANA time zone data"
optional = false
python-versions = ">=2"
groups = ["main"]
files = [
- {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
- {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
+ {file = "tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1"},
+ {file = "tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7"},
]
[[package]]
name = "urllib3"
-version = "2.5.0"
+version = "2.6.2"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.9"
groups = ["main", "dev", "type-checking"]
files = [
- {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
- {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
+ {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"},
+ {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"},
]
[package.extras]
-brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""]
+brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""]
h2 = ["h2 (>=4,<5)"]
socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
-zstd = ["zstandard (>=0.18.0)"]
+zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""]
[[package]]
name = "uvicorn"
-version = "0.38.0"
+version = "0.40.0"
description = "The lightning-fast ASGI server."
optional = false
-python-versions = ">=3.9"
+python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02"},
- {file = "uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d"},
+ {file = "uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee"},
+ {file = "uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea"},
]
[package.dependencies]
@@ -2380,18 +2389,18 @@ standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)
[[package]]
name = "werkzeug"
-version = "3.1.1"
+version = "3.1.4"
description = "The comprehensive WSGI web application library."
optional = false
python-versions = ">=3.9"
groups = ["main"]
files = [
- {file = "werkzeug-3.1.1-py3-none-any.whl", hash = "sha256:a71124d1ef06008baafa3d266c02f56e1836a5984afd6dd6c9230669d60d9fb5"},
- {file = "werkzeug-3.1.1.tar.gz", hash = "sha256:8cd39dfbdfc1e051965f156163e2974e52c210f130810e9ad36858f0fd3edad4"},
+ {file = "werkzeug-3.1.4-py3-none-any.whl", hash = "sha256:2ad50fb9ed09cc3af22c54698351027ace879a0b60a3b5edf5730b2f7d876905"},
+ {file = "werkzeug-3.1.4.tar.gz", hash = "sha256:cd3cd98b1b92dc3b7b3995038826c68097dcb16f9baa63abe35f20eafeb9fe5e"},
]
[package.dependencies]
-MarkupSafe = ">=2.1.1"
+markupsafe = ">=2.1.1"
[package.extras]
watchdog = ["watchdog (>=2.3)"]
@@ -2399,4 +2408,4 @@ watchdog = ["watchdog (>=2.3)"]
[metadata]
lock-version = "2.1"
python-versions = ">=3.10, <4"
-content-hash = "b7ea95a1a29ef5ce4aec7eb44e253601508ae6b67b5d1091db3e5dc0d4252e8c"
+content-hash = "1007af3ad0f6fd0278abe498547d74e90b5870a522754706c50f85673d11f88b"
diff --git a/pyproject.toml b/pyproject.toml
index 121ba48..0b3c0fe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[project]
name="robotframework-openapitools"
-version = "1.0.5"
+version = "2.0.0b1"
description = "A set of Robot Framework libraries to test APIs for which the OAS is available."
authors = [
{name = "Robin Mackaij", email = "r.a.mackaij@gmail.com"},
@@ -28,12 +28,11 @@ dependencies = [
"robotframework >= 6.0.0, !=7.0.0",
"robotframework-datadriver >= 1.10.0",
"requests >= 2.31.0",
- "prance[CLI] >= 23",
- "Faker >= 23.1.0",
+ "prance[CLI] >= 25",
+ "Faker >= 38.0.0",
"rstr >= 3.2.0",
"openapi-core >= 0.19.0",
"rich_click >= 1.7.0",
- "black >= 24.1.0",
"Jinja2 >= 3.1.2",
"pydantic >= 2.11.0",
]
@@ -42,23 +41,23 @@ dependencies = [
dev = [
"invoke >= 2.2.0",
"robotframework-stacktrace >= 0.4.0",
- "uvicorn >= 0.27.0",
- "fastapi >= 0.109.0",
+ "uvicorn >= 0.38.0",
+ "fastapi >= 0.122.0",
"coverage[toml] >= 7.2.0",
- "robotcode-runner >= 1.0.3",
+ "robotcode-runner >= 2.0.0",
"genbadge[coverage] >= 1.1.2",
]
type-checking = [
"mypy >= 1.14.1",
"types-requests >= 2.31.0",
"types-invoke >= 2.0.0.0",
- "pyright >= 1.1.350",
- "robotcode-analyze >= 1.0.3",
+ "pyright >= 1.1.400",
+ "robotcode-analyze >= 2.0.0",
]
lint-and-format = [
- "ruff >= 0.9.0",
- "pylint >= 3.3.3",
- "robotframework-robocop >= 5.7.0",
+ "ruff >= 0.14.0",
+ "pylint >= 4.0.0",
+ "robotframework-robocop >= 6.0.0",
]
[project.urls]
@@ -108,7 +107,7 @@ build-backend = "poetry.core.masonry.api"
branch = true
parallel = true
source = ["src/OpenApiDriver", "src/OpenApiLibCore", "src/openapi_libgen"]
-omit = ["src/openapi_libgen/command_line.py"]
+omit = ["src/openapi_libgen/command_line.py", "src/OpenApiLibCore/protocols.py"]
[tool.coverage.report]
exclude_lines = [
@@ -126,6 +125,7 @@ disallow_untyped_defs = true
strict = true
show_error_codes = true
exclude = []
+follow_untyped_imports = true
[[tool.mypy.overrides]]
module = [
diff --git a/src/OpenApiDriver/__init__.py b/src/OpenApiDriver/__init__.py
index 7557256..c3e54a9 100644
--- a/src/OpenApiDriver/__init__.py
+++ b/src/OpenApiDriver/__init__.py
@@ -6,15 +6,17 @@
- IdDependency, IdReference, PathPropertiesConstraint, PropertyValueConstraint,
UniquePropertyValueConstraint: Classes to be subclassed by the library user
when implementing a custom mapping module (advanced use).
-- Dto, Relation: Base classes that can be used for type annotations.
+- RelationsMapping, Relation: Base classes that can be used for type annotations.
- IGNORE: A special constant that can be used as a value in the PropertyValueConstraint.
"""
from importlib.metadata import version
from OpenApiDriver.openapidriver import OpenApiDriver
-from OpenApiLibCore.dto_base import (
- Dto,
+from OpenApiLibCore.data_relations.relations_base import RelationsMapping
+from OpenApiLibCore.keyword_logic.validation import ValidationLevel
+from OpenApiLibCore.models import IGNORE
+from OpenApiLibCore.models.resource_relations import (
IdDependency,
IdReference,
PathPropertiesConstraint,
@@ -22,8 +24,6 @@
ResourceRelation,
UniquePropertyValueConstraint,
)
-from OpenApiLibCore.validation import ValidationLevel
-from OpenApiLibCore.value_utils import IGNORE
try:
__version__ = version("robotframework-openapidriver")
@@ -33,12 +33,12 @@
__all__ = [
"IGNORE",
- "Dto",
"IdDependency",
"IdReference",
"OpenApiDriver",
"PathPropertiesConstraint",
"PropertyValueConstraint",
+ "RelationsMapping",
"ResourceRelation",
"UniquePropertyValueConstraint",
"ValidationLevel",
diff --git a/src/OpenApiDriver/openapi_executors.py b/src/OpenApiDriver/openapi_executors.py
index 3daa00d..c56e4cb 100644
--- a/src/OpenApiDriver/openapi_executors.py
+++ b/src/OpenApiDriver/openapi_executors.py
@@ -6,6 +6,7 @@
from pathlib import Path
from random import choice
from types import MappingProxyType
+from typing import Literal, overload
from requests import Response
from requests.auth import AuthBase
@@ -21,10 +22,10 @@
from OpenApiLibCore import (
OpenApiLibCore,
RequestData,
- RequestValues,
ValidationLevel,
)
from OpenApiLibCore.annotations import JSON
+from OpenApiLibCore.models.oas_models import ObjectSchema
run_keyword = BuiltIn().run_keyword
default_str_mapping: Mapping[str, str] = MappingProxyType({})
@@ -38,6 +39,52 @@
]
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_valid_url"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_invalidated_url"], *args: str | int
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["authorized_request"], *args: object
+) -> Response: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_request_data"], *args: str
+) -> RequestData: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_invalid_body_data"], *args: object
+) -> dict[str, JSON] | list[JSON]: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_invalidated_parameters"], *args: object
+) -> tuple[dict[str, JSON], dict[str, str]]: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["validated_request"], *args: object
+) -> None: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
@library(scope="SUITE", doc_format="ROBOT")
class OpenApiExecutors(OpenApiLibCore):
"""Main class providing the keywords and core logic to perform endpoint validations."""
@@ -50,7 +97,7 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
response_validation: ValidationLevel = ValidationLevel.WARN,
disable_server_validation: bool = True,
mappings_path: str | Path = "",
- invalid_property_default_response: int = 422,
+ invalid_data_default_response: int = 422,
default_id_property_name: str = "id",
faker_locale: str | list[str] = "",
require_body_for_invalid_url: bool = False,
@@ -74,7 +121,7 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
disable_server_validation=disable_server_validation,
mappings_path=mappings_path,
default_id_property_name=default_id_property_name,
- invalid_property_default_response=invalid_property_default_response,
+ invalid_data_default_response=invalid_data_default_response,
faker_locale=faker_locale,
require_body_for_invalid_url=require_body_for_invalid_url,
recursion_limit=recursion_limit,
@@ -102,7 +149,7 @@ def test_unauthorized(self, path: str, method: str) -> None:
> Note: No headers or (json) body are send with the request. For security
reasons, the authorization validation should be checked first.
"""
- url: str = run_keyword("get_valid_url", path)
+ url = _run_keyword("get_valid_url", path)
response = self.session.request(
method=method,
url=url,
@@ -123,8 +170,8 @@ def test_forbidden(self, path: str, method: str) -> None:
> Note: No headers or (json) body are send with the request. For security
reasons, the access rights validation should be checked first.
"""
- url: str = run_keyword("get_valid_url", path)
- response: Response = run_keyword("authorized_request", url, method)
+ url = _run_keyword("get_valid_url", path)
+ response = _run_keyword("authorized_request", url, method)
if response.status_code != int(HTTPStatus.FORBIDDEN):
raise AssertionError(f"Response {response.status_code} was not 403.")
@@ -148,12 +195,10 @@ def test_invalid_url(
parameters are send with the request. The `require_body_for_invalid_url`
parameter can be set to `True` if needed.
"""
- valid_url: str = run_keyword("get_valid_url", path)
+ valid_url = _run_keyword("get_valid_url", path)
try:
- url = run_keyword(
- "get_invalidated_url", valid_url, path, expected_status_code
- )
+ url = _run_keyword("get_invalidated_url", valid_url, expected_status_code)
except Exception as exception:
message = getattr(exception, "message", "")
if not message.startswith("ValueError"):
@@ -166,12 +211,11 @@ def test_invalid_url(
params, headers, json_data = None, None, None
if self.require_body_for_invalid_url:
- request_data: RequestData = run_keyword("get_request_data", path, method)
+ request_data = _run_keyword("get_request_data", path, method)
params = request_data.params
headers = request_data.headers
- dto = request_data.dto
- json_data = dto.as_dict()
- response: Response = run_keyword(
+ json_data = request_data.valid_data
+ response = _run_keyword(
"authorized_request", url, method, params, headers, json_data
)
if response.status_code != expected_status_code:
@@ -191,68 +235,63 @@ def test_endpoint(self, path: str, method: str, status_code: int) -> None:
The keyword calls other keywords to generate the neccesary data to perform
the desired operation and validate the response against the openapi document.
"""
- json_data: dict[str, JSON] = {}
original_data = {}
- url: str = run_keyword("get_valid_url", path)
- request_data: RequestData = run_keyword("get_request_data", path, method)
+ url = _run_keyword("get_valid_url", path)
+ request_data = _run_keyword("get_request_data", path, method)
params = request_data.params
headers = request_data.headers
- if request_data.has_body:
- json_data = request_data.dto.as_dict()
+ json_data = request_data.valid_data
# when patching, get the original data to check only patched data has changed
if method == "PATCH":
original_data = self.get_original_data(url=url)
# in case of a status code indicating an error, ensure the error occurs
if status_code >= int(HTTPStatus.BAD_REQUEST):
- invalidation_keyword_data = {
- "get_invalid_body_data": [
- "get_invalid_body_data",
- url,
- method,
- status_code,
- request_data,
- ],
- "get_invalidated_parameters": [
- "get_invalidated_parameters",
- status_code,
- request_data,
- ],
- }
- invalidation_keywords = []
-
- if request_data.dto.get_body_relations_for_error_code(status_code):
+ invalidation_keywords: list[str] = []
+
+ if request_data.relations_mapping.get_body_relations_for_error_code(
+ status_code
+ ):
invalidation_keywords.append("get_invalid_body_data")
- if request_data.dto.get_parameter_relations_for_error_code(status_code):
+ if request_data.relations_mapping.get_parameter_relations_for_error_code(
+ status_code
+ ):
invalidation_keywords.append("get_invalidated_parameters")
if invalidation_keywords:
- if (
- invalidation_keyword := choice(invalidation_keywords)
- ) == "get_invalid_body_data":
- json_data = run_keyword(
- *invalidation_keyword_data[invalidation_keyword]
+ invalidation_keyword = choice(invalidation_keywords)
+ if invalidation_keyword == "get_invalid_body_data":
+ json_data = _run_keyword(
+ "get_invalid_body_data",
+ url,
+ method,
+ status_code,
+ request_data,
)
else:
- params, headers = run_keyword(
- *invalidation_keyword_data[invalidation_keyword]
+ params, headers = _run_keyword(
+ "get_invalidated_parameters", status_code, request_data
)
# if there are no relations to invalide and the status_code is the default
# response_code for invalid properties, invalidate properties instead
- elif status_code == self.invalid_property_default_response:
+ elif status_code == self.invalid_data_default_response:
if (
request_data.params_that_can_be_invalidated
or request_data.headers_that_can_be_invalidated
):
- params, headers = run_keyword(
- *invalidation_keyword_data["get_invalidated_parameters"]
+ params, headers = _run_keyword(
+ "get_invalidated_parameters", status_code, request_data
)
if request_data.body_schema:
- json_data = run_keyword(
- *invalidation_keyword_data["get_invalid_body_data"]
+ json_data = _run_keyword(
+ "get_invalid_body_data",
+ url,
+ method,
+ status_code,
+ request_data,
)
elif request_data.body_schema:
- json_data = run_keyword(
- *invalidation_keyword_data["get_invalid_body_data"]
+ json_data = _run_keyword(
+ "get_invalid_body_data", url, method, status_code, request_data
)
else:
raise SkipExecution(
@@ -260,19 +299,17 @@ def test_endpoint(self, path: str, method: str, status_code: int) -> None:
)
else:
raise AssertionError(
- f"No Dto mapping found to cause status_code {status_code}."
+ f"No relation found to cause status_code {status_code}."
)
- run_keyword(
- "perform_validated_request",
+ _run_keyword(
+ "validated_request",
path,
status_code,
- RequestValues(
- url=url,
- method=method,
- params=params,
- headers=headers,
- json_data=json_data,
- ),
+ url,
+ method,
+ params,
+ headers,
+ json_data,
original_data,
)
if status_code < int(HTTPStatus.MULTIPLE_CHOICES) and (
@@ -281,27 +318,28 @@ def test_endpoint(self, path: str, method: str, status_code: int) -> None:
or request_data.has_optional_headers
):
logger.info("Performing request without optional properties and parameters")
- url = run_keyword("get_valid_url", path)
- request_data = run_keyword("get_request_data", path, method)
+ url = _run_keyword("get_valid_url", path)
+ request_data = _run_keyword("get_request_data", path, method)
params = request_data.get_required_params()
headers = request_data.get_required_headers()
- json_data = (
- request_data.get_minimal_body_dict() if request_data.has_body else {}
- )
+ if isinstance(request_data.body_schema, ObjectSchema):
+ json_data = (
+ request_data.get_minimal_body_dict()
+ if request_data.has_body
+ else {}
+ )
original_data = {}
if method == "PATCH":
original_data = self.get_original_data(url=url)
- run_keyword(
- "perform_validated_request",
+ _run_keyword(
+ "validated_request",
path,
status_code,
- RequestValues(
- url=url,
- method=method,
- params=params,
- headers=headers,
- json_data=json_data,
- ),
+ url,
+ method,
+ params,
+ headers,
+ json_data,
original_data,
)
@@ -313,10 +351,10 @@ def get_original_data(self, url: str) -> dict[str, JSON]:
"""
original_data = {}
path = self.get_parameterized_path_from_url(url)
- get_request_data: RequestData = run_keyword("get_request_data", path, "GET")
+ get_request_data = _run_keyword("get_request_data", path, "GET")
get_params = get_request_data.params
get_headers = get_request_data.headers
- response: Response = run_keyword(
+ response = _run_keyword(
"authorized_request", url, "GET", get_params, get_headers
)
if response.ok:
@@ -327,5 +365,5 @@ def get_original_data(self, url: str) -> dict[str, JSON]:
def get_keyword_names() -> list[str]:
"""Curated keywords for libdoc and libspec."""
if getenv("HIDE_INHERITED_KEYWORDS") == "true":
- return KEYWORD_NAMES
+ return KEYWORD_NAMES # pragma: no cover
return KEYWORD_NAMES + LIBCORE_KEYWORD_NAMES
diff --git a/src/OpenApiDriver/openapi_reader.py b/src/OpenApiDriver/openapi_reader.py
index 90be78d..44d8afe 100644
--- a/src/OpenApiDriver/openapi_reader.py
+++ b/src/OpenApiDriver/openapi_reader.py
@@ -5,7 +5,7 @@
from DataDriver.AbstractReaderClass import AbstractReaderClass
from DataDriver.ReaderConfig import TestCaseData
-from OpenApiLibCore.models import PathItemObject
+from OpenApiLibCore.models.oas_models import PathItemObject
class Test:
@@ -45,7 +45,7 @@ def get_data_from_source(self) -> list[TestCaseData]:
ignored_tests = [Test(*test) for test in getattr(self, "ignored_testcases", [])]
for path, path_item in paths.items():
- path_operations = path_item.get_operations()
+ path_operations = path_item.operations
# by reseversing the items, post/put operations come before get and delete
for method, operation_data in reversed(path_operations.items()):
diff --git a/src/OpenApiDriver/openapidriver.libspec b/src/OpenApiDriver/openapidriver.libspec
index ffcf5e6..f4d6fca 100644
--- a/src/OpenApiDriver/openapidriver.libspec
+++ b/src/OpenApiDriver/openapidriver.libspec
@@ -1,6 +1,6 @@
-
-1.0.5
+
+2.0.0b1The OpenApiDriver library provides the keywords and logic for execution of generated test cases based on an OpenAPI document.
Visit the <a href="./index.html" target="_blank">OpenApiTools documentation</a> for an introduction.
@@ -8,7 +8,7 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
-
+source
@@ -73,8 +73,8 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
-
-invalid_property_default_response
+
+invalid_data_default_response422
@@ -152,7 +152,7 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
extra_headers
-
+
@@ -161,11 +161,11 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
cookies
-
+
-
+None
@@ -173,7 +173,7 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
proxies
-
+
@@ -242,7 +242,7 @@ by Response validation.
<h3>mappings_path</h3>
See the Advanced Use tab for an in-depth explanation.
-<h3>invalid_property_default_response</h3>
+<h3>invalid_data_default_response</h3>
The default response code for requests with a JSON body that does not comply
with the schema.
Example: a value outside the specified range or a string value
@@ -325,7 +325,7 @@ A dictionary of <code>"protocol": "proxy url"</code> to use for all
-
+path
@@ -350,7 +350,7 @@ The keyword calls other keywords to generate the neccesary data to perform
the desired operation and validate the response against the openapi document.
Validate that performing the `method` operation on `path` results in a `status_code` response.
-
+path
@@ -371,7 +371,7 @@ library should grant insufficient access rights to the target endpoint.
reasons, the access rights validation should be checked first.
Perform a request for `method` on the `path`, with the provided authorization.
-
+path
@@ -403,7 +403,7 @@ parameters are send with the request. The <span class="name">require_body_
parameter can be set to <span class="name">True</span> if needed.
Perform a request for the provided 'path' and 'method' where the url for the `path` is invalidated.
-
+path
@@ -427,7 +427,7 @@ reasons, the authorization validation should be checked first.
-<p>Strings <code>TRUE</code>, <code>YES</code>, <code>ON</code> and <code>1</code> are converted to Boolean <code>True</code>, the empty string as well as strings <code>FALSE</code>, <code>NO</code>, <code>OFF</code> and <code>0</code> are converted to Boolean <code>False</code>, and the string <code>NONE</code> is converted to the Python <code>None</code> object. Other strings and other accepted values are passed as-is, allowing keywords to handle them specially if needed. All string comparisons are case-insensitive.</p>
+<p>Strings <code>TRUE</code>, <code>YES</code>, <code>ON</code>, <code>1</code> and possible localization specific "true strings" are converted to Boolean <code>True</code>, the empty string, strings <code>FALSE</code>, <code>NO</code>, <code>OFF</code> and <code>0</code> and possibly localization specific "false strings" are converted to Boolean <code>False</code>, and the string <code>NONE</code> is converted to the Python <code>None</code> object. Other strings and all other values are passed as-is, allowing keywords to handle them specially if needed. All string comparisons are case-insensitive.</p>
<p>Examples: <code>TRUE</code> (converted to <code>True</code>), <code>off</code> (converted to <code>False</code>), <code>example</code> (used as-is)</p>string
@@ -439,22 +439,9 @@ reasons, the authorization validation should be checked first.__init__
-
-<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#dict">dictionary</a> literals. They are converted to actual dictionaries using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including dictionaries and other containers.</p>
-<p>If the type has nested types like <code>dict[str, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
-<p>Examples: <code>{'a': 1, 'b': 2}</code>, <code>{'key': 1, 'nested': {'key': 2}}</code></p>
-
-string
-Mapping
-
-
-__init__
-
-<p>Conversion is done using Python's <a href="https://docs.python.org/library/functions.html#int">int</a> built-in function. Floating point numbers are accepted only if they can be represented as integers exactly. For example, <code>1.0</code> is accepted and <code>1.1</code> is not.</p>
-<p>Starting from RF 4.1, it is possible to use hexadecimal, octal and binary numbers by prefixing values with <code>0x</code>, <code>0o</code> and <code>0b</code>, respectively.</p>
-<p>Starting from RF 4.1, spaces and underscores can be used as visual separators for digit grouping purposes.</p>
+<p>It is possible to use hexadecimal, octal and binary numbers by prefixing values with <code>0x</code>, <code>0o</code> and <code>0b</code>, respectively. Spaces and underscores can be used as visual separators for digit grouping purposes.</p>
<p>Examples: <code>42</code>, <code>-1</code>, <code>0b1010</code>, <code>10 000 000</code>, <code>0xBAD_C0FFEE</code></p>string
@@ -467,9 +454,11 @@ reasons, the authorization validation should be checked first.
-<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#list">list</a> literals. They are converted to actual lists using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including lists and other containers.</p>
-<p>If the type has nested types like <code>list[int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
-<p>Examples: <code>['one', 'two']</code>, <code>[('one', 1), ('two', 2)]</code></p>
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#list">list</a> or <a href="https://docs.python.org/library/stdtypes.html#tuple">tuple</a> literals. They are converted using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function and possible tuples converted further to lists. They can contain any values <code>ast.literal_eval</code> supports, including lists and other collections.</p>
+<p>If the argument is a list, it is used without conversion. Tuples and other sequences are converted to lists.</p>
+<p>If the type has nested types like <code>list[int]</code>, items are converted to those types automatically.</p>
+<p>Examples: <code>['one', 'two']</code>, <code>[('one', 1), ('two', 2)]</code></p>
+<p>Support to convert nested types is new in Robot Framework 6.0. Support for tuple literals is new in Robot Framework 7.4.</p>stringSequence
@@ -478,8 +467,22 @@ reasons, the authorization validation should be checked first.
__init__
+
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#dict">dictionary</a> literals. They are converted to actual dictionaries using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including dictionaries and other collections.</p>
+<p>Any mapping is accepted without conversion. An exception is that if the type is <code>MutableMapping</code>, immutable values are converted to <code>dict</code>.</p>
+<p>If the type has nested types like <code>Mapping[str, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
+<p>Examples: <code>{'a': 1, 'b': 2}</code>, <code>{'key': 1, 'nested': {'key': 2}}</code></p>
+
+string
+Mapping
+
+
+__init__
+
+
-<p>String <code>NONE</code> (case-insensitive) is converted to Python <code>None</code> object. Other values cause an error.</p>
+<p>String <code>NONE</code> (case-insensitive) and the empty string are converted to the Python <code>None</code> object. Other values cause an error.</p>
+<p>Converting the empty string is new in Robot Framework 7.4.</p>string
@@ -499,7 +502,9 @@ reasons, the authorization validation should be checked first.
-<p>All arguments are converted to Unicode strings.</p>
+<p>All arguments are converted to Unicode strings.</p>
+<p>Most values are converted simply by using <code>str(value)</code>. An exception is that bytes are mapped directly to Unicode code points with same ordinals. This means that, for example, <code>b"hyv\xe4"</code> becomes <code>"hyvä"</code>.</p>
+<p>Converting bytes specially is new Robot Framework 7.4.</p>Any
@@ -512,9 +517,11 @@ reasons, the authorization validation should be checked first.
-<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#tuple">tuple</a> literals. They are converted to actual tuples using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including tuples and other containers.</p>
-<p>If the type has nested types like <code>tuple[str, int, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
-<p>Examples: <code>('one', 'two')</code>, <code>(('one', 1), ('two', 2))</code></p>
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#tuple">tuple</a> or <a href="https://docs.python.org/library/stdtypes.html#list">list</a> literals. They are converted using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function and possible lists converted further to tuples. They can contain any values <code>ast.literal_eval</code> supports, including tuples and other collections.</p>
+<p>If the argument is a tuple, it is used without conversion. Lists and other sequences are converted to tuples.</p>
+<p>If the type has nested types like <code>tuple[str, int, int]</code>, items are converted to those types automatically.</p>
+<p>Examples: <code>('one', 'two')</code>, <code>(('one', 1), ('two', 2))</code></p>
+<p>Support to convert nested types is new in Robot Framework 6.0. Support for list literals is new in Robot Framework 7.4.</p>stringSequence
diff --git a/src/OpenApiDriver/openapidriver.py b/src/OpenApiDriver/openapidriver.py
index 5f3026d..ded7560 100644
--- a/src/OpenApiDriver/openapidriver.py
+++ b/src/OpenApiDriver/openapidriver.py
@@ -34,7 +34,7 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
response_validation: ValidationLevel = ValidationLevel.WARN,
disable_server_validation: bool = True,
mappings_path: str | Path = "",
- invalid_property_default_response: int = 422,
+ invalid_data_default_response: int = 422,
default_id_property_name: str = "id",
faker_locale: str | list[str] = "",
require_body_for_invalid_url: bool = False,
@@ -64,7 +64,7 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
response_validation=response_validation,
disable_server_validation=disable_server_validation,
mappings_path=mappings_path,
- invalid_property_default_response=invalid_property_default_response,
+ invalid_data_default_response=invalid_data_default_response,
default_id_property_name=default_id_property_name,
faker_locale=faker_locale,
require_body_for_invalid_url=require_body_for_invalid_url,
@@ -84,7 +84,7 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
read_paths_method = self.read_paths
DataDriver.__init__(
self,
- reader_class=OpenApiReader,
+ reader_class=OpenApiReader, # type: ignore[arg-type]
read_paths_method=read_paths_method,
included_paths=included_paths,
ignored_paths=ignored_paths,
diff --git a/src/OpenApiLibCore/__init__.py b/src/OpenApiLibCore/__init__.py
index b4d373e..27d4e82 100644
--- a/src/OpenApiLibCore/__init__.py
+++ b/src/OpenApiLibCore/__init__.py
@@ -7,14 +7,17 @@
- IdDependency, IdReference, PathPropertiesConstraint, PropertyValueConstraint,
UniquePropertyValueConstraint: Classes to be subclassed by the library user
when implementing a custom mapping module (advanced use).
-- Dto, Relation: Base classes that can be used for type annotations.
+- RelationsMapping, Relation: Base classes that can be used for type annotations.
- IGNORE: A special constant that can be used as a value in the PropertyValueConstraint.
"""
from importlib.metadata import version
-from OpenApiLibCore.dto_base import (
- Dto,
+from OpenApiLibCore.data_relations.relations_base import RelationsMapping
+from OpenApiLibCore.keyword_logic.validation import ValidationLevel
+from OpenApiLibCore.models import IGNORE, UNSET
+from OpenApiLibCore.models.request_data import RequestData, RequestValues
+from OpenApiLibCore.models.resource_relations import (
IdDependency,
IdReference,
PathPropertiesConstraint,
@@ -22,13 +25,7 @@
ResourceRelation,
UniquePropertyValueConstraint,
)
-from OpenApiLibCore.dto_utils import DefaultDto
-from OpenApiLibCore.openapi_libcore import (
- OpenApiLibCore,
-)
-from OpenApiLibCore.request_data import RequestData, RequestValues
-from OpenApiLibCore.validation import ValidationLevel
-from OpenApiLibCore.value_utils import IGNORE, UNSET
+from OpenApiLibCore.openapi_libcore import OpenApiLibCore
try:
__version__ = version("robotframework-openapi-libcore")
@@ -43,6 +40,8 @@
"set_auth",
"set_extra_headers",
"get_request_values",
+ "get_request_values_object",
+ "convert_request_values_to_dict",
"get_request_data",
"get_invalid_body_data",
"get_invalidated_parameters",
@@ -54,6 +53,8 @@
"get_invalidated_url",
"ensure_in_use",
"authorized_request",
+ "perform_authorized_request",
+ "validated_request",
"perform_validated_request",
"validate_response_using_validator",
"assert_href_to_resource_is_valid",
@@ -65,13 +66,12 @@
__all__ = [
"IGNORE",
"UNSET",
- "DefaultDto",
- "Dto",
"IdDependency",
"IdReference",
"OpenApiLibCore",
"PathPropertiesConstraint",
"PropertyValueConstraint",
+ "RelationsMapping",
"RequestData",
"RequestValues",
"ResourceRelation",
diff --git a/src/OpenApiLibCore/annotations.py b/src/OpenApiLibCore/annotations.py
index fa70867..2cb6a5e 100644
--- a/src/OpenApiLibCore/annotations.py
+++ b/src/OpenApiLibCore/annotations.py
@@ -6,5 +6,5 @@
JSON = TypeAliasType(
"JSON",
- "Union[dict[str, JSON], list[JSON], str, bytes, int, float, bool, None]",
+ "Union[dict[str, JSON], list[JSON], str, int, float, bool, None]",
)
diff --git a/src/OpenApiLibCore/data_generation/__init__.py b/src/OpenApiLibCore/data_generation/__init__.py
index be5f703..c41511c 100644
--- a/src/OpenApiLibCore/data_generation/__init__.py
+++ b/src/OpenApiLibCore/data_generation/__init__.py
@@ -2,9 +2,3 @@
Module holding the functions related to data generation
for the requests made as part of keyword exection.
"""
-
-from .data_generation_core import get_request_data
-
-__all__ = [
- "get_request_data",
-]
diff --git a/src/OpenApiLibCore/data_generation/body_data_generation.py b/src/OpenApiLibCore/data_generation/body_data_generation.py
deleted file mode 100644
index 0ae0270..0000000
--- a/src/OpenApiLibCore/data_generation/body_data_generation.py
+++ /dev/null
@@ -1,250 +0,0 @@
-"""
-Module holding the functions related to (json) data generation
-for the body of requests made as part of keyword exection.
-"""
-
-from random import choice, randint, sample
-from typing import Any
-
-from robot.api import logger
-
-import OpenApiLibCore.path_functions as _path_functions
-from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.dto_base import (
- Dto,
- IdDependency,
- PropertyValueConstraint,
-)
-from OpenApiLibCore.dto_utils import DefaultDto
-from OpenApiLibCore.models import (
- ArraySchema,
- ObjectSchema,
- SchemaObjectTypes,
- UnionTypeSchema,
-)
-from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
-from OpenApiLibCore.protocols import GetIdPropertyNameType
-from OpenApiLibCore.value_utils import IGNORE
-
-
-def get_json_data_for_dto_class(
- schema: SchemaObjectTypes,
- dto_class: type[Dto],
- get_id_property_name: GetIdPropertyNameType,
- operation_id: str | None = None,
-) -> JSON:
- if isinstance(schema, UnionTypeSchema):
- chosen_schema = choice(schema.resolved_schemas)
- return get_json_data_for_dto_class(
- schema=chosen_schema,
- dto_class=dto_class,
- get_id_property_name=get_id_property_name,
- operation_id=operation_id,
- )
-
- match schema:
- case ObjectSchema():
- return get_dict_data_for_dto_class(
- schema=schema,
- dto_class=dto_class,
- get_id_property_name=get_id_property_name,
- operation_id=operation_id,
- )
- case ArraySchema():
- return get_list_data_for_dto_class(
- schema=schema,
- dto_class=dto_class,
- get_id_property_name=get_id_property_name,
- operation_id=operation_id,
- )
- case _:
- return schema.get_valid_value()
-
-
-def get_dict_data_for_dto_class(
- schema: ObjectSchema,
- dto_class: type[Dto],
- get_id_property_name: GetIdPropertyNameType,
- operation_id: str | None = None,
-) -> dict[str, Any]:
- json_data: dict[str, Any] = {}
-
- property_names = get_property_names_to_process(schema=schema, dto_class=dto_class)
-
- for property_name in property_names:
- property_schema = schema.properties.root[property_name] # type: ignore[union-attr]
- if property_schema.readOnly:
- continue
-
- json_data[property_name] = get_data_for_property(
- property_name=property_name,
- property_schema=property_schema,
- get_id_property_name=get_id_property_name,
- dto_class=dto_class,
- operation_id=operation_id,
- )
-
- return json_data
-
-
-def get_list_data_for_dto_class(
- schema: ArraySchema,
- dto_class: type[Dto],
- get_id_property_name: GetIdPropertyNameType,
- operation_id: str | None = None,
-) -> list[JSON]:
- json_data: list[JSON] = []
- list_item_schema = schema.items
- min_items = schema.minItems if schema.minItems is not None else 0
- max_items = schema.maxItems if schema.maxItems is not None else 1
- number_of_items_to_generate = randint(min_items, max_items)
- for _ in range(number_of_items_to_generate):
- list_item_data = get_json_data_for_dto_class(
- schema=list_item_schema,
- dto_class=dto_class,
- get_id_property_name=get_id_property_name,
- operation_id=operation_id,
- )
- json_data.append(list_item_data)
- return json_data
-
-
-def get_data_for_property(
- property_name: str,
- property_schema: SchemaObjectTypes,
- get_id_property_name: GetIdPropertyNameType,
- dto_class: type[Dto],
- operation_id: str | None,
-) -> JSON:
- if constrained_values := get_constrained_values(
- dto_class=dto_class, property_name=property_name
- ):
- constrained_value = choice(constrained_values)
- # Check if the chosen value is a nested Dto; since a Dto is never
- # instantiated, we can use isinstance(..., type) for this.
- if isinstance(constrained_value, type):
- return get_value_constrained_by_nested_dto(
- property_schema=property_schema,
- nested_dto_class=constrained_value,
- get_id_property_name=get_id_property_name,
- operation_id=operation_id,
- )
- return constrained_value
-
- if (
- dependent_id := get_dependent_id(
- dto_class=dto_class,
- property_name=property_name,
- operation_id=operation_id,
- get_id_property_name=get_id_property_name,
- )
- ) is not None:
- return dependent_id
-
- return get_json_data_for_dto_class(
- schema=property_schema,
- dto_class=DefaultDto,
- get_id_property_name=get_id_property_name,
- )
-
-
-def get_value_constrained_by_nested_dto(
- property_schema: SchemaObjectTypes,
- nested_dto_class: type[Dto],
- get_id_property_name: GetIdPropertyNameType,
- operation_id: str | None,
-) -> JSON:
- nested_schema = get_schema_for_nested_dto(property_schema=property_schema)
- nested_value = get_json_data_for_dto_class(
- schema=nested_schema,
- dto_class=nested_dto_class,
- get_id_property_name=get_id_property_name,
- operation_id=operation_id,
- )
- return nested_value
-
-
-def get_schema_for_nested_dto(property_schema: SchemaObjectTypes) -> SchemaObjectTypes:
- if isinstance(property_schema, UnionTypeSchema):
- chosen_schema = choice(property_schema.resolved_schemas)
- return get_schema_for_nested_dto(chosen_schema)
-
- return property_schema
-
-
-def get_property_names_to_process(
- schema: ObjectSchema,
- dto_class: type[Dto],
-) -> list[str]:
- property_names = []
-
- for property_name in schema.properties.root: # type: ignore[union-attr]
- # register the oas_name
- _ = get_safe_name_for_oas_name(property_name)
- if constrained_values := get_constrained_values(
- dto_class=dto_class, property_name=property_name
- ):
- # do not add properties that are configured to be ignored
- if IGNORE in constrained_values: # type: ignore[comparison-overlap]
- continue
- property_names.append(property_name)
-
- max_properties = schema.maxProperties
- if max_properties and len(property_names) > max_properties:
- required_properties = schema.required
- number_of_optional_properties = max_properties - len(required_properties)
- optional_properties = [
- name for name in property_names if name not in required_properties
- ]
- selected_optional_properties = sample(
- optional_properties, number_of_optional_properties
- )
- property_names = required_properties + selected_optional_properties
-
- return property_names
-
-
-def get_constrained_values(
- dto_class: type[Dto], property_name: str
-) -> list[JSON | type[Dto]]:
- relations = dto_class.get_relations()
- values_list = [
- c.values
- for c in relations
- if (isinstance(c, PropertyValueConstraint) and c.property_name == property_name)
- ]
- # values should be empty or contain 1 list of allowed values
- return values_list.pop() if values_list else []
-
-
-def get_dependent_id(
- dto_class: type[Dto],
- property_name: str,
- operation_id: str | None,
- get_id_property_name: GetIdPropertyNameType,
-) -> str | int | float | None:
- relations = dto_class.get_relations()
- # multiple get paths are possible based on the operation being performed
- id_get_paths = [
- (d.get_path, d.operation_id)
- for d in relations
- if (isinstance(d, IdDependency) and d.property_name == property_name)
- ]
- if not id_get_paths:
- return None
- if len(id_get_paths) == 1:
- id_get_path, _ = id_get_paths.pop()
- else:
- try:
- [id_get_path] = [
- path for path, operation in id_get_paths if operation == operation_id
- ]
- # There could be multiple get_paths, but not one for the current operation
- except ValueError:
- return None
-
- valid_id = _path_functions.get_valid_id_for_path(
- path=id_get_path, get_id_property_name=get_id_property_name
- )
- logger.debug(f"get_dependent_id for {id_get_path} returned {valid_id}")
- return valid_id
diff --git a/src/OpenApiLibCore/data_generation/data_generation_core.py b/src/OpenApiLibCore/data_generation/data_generation_core.py
index 71f3ac6..a041cbb 100644
--- a/src/OpenApiLibCore/data_generation/data_generation_core.py
+++ b/src/OpenApiLibCore/data_generation/data_generation_core.py
@@ -10,68 +10,65 @@
from robot.api import logger
-import OpenApiLibCore.path_functions as _path_functions
+import OpenApiLibCore.keyword_logic.path_functions as _path_functions
from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.dto_base import (
- Dto,
- PropertyValueConstraint,
- ResourceRelation,
-)
-from OpenApiLibCore.dto_utils import DefaultDto
-from OpenApiLibCore.models import (
+from OpenApiLibCore.data_relations.relations_base import RelationsMapping
+from OpenApiLibCore.models import IGNORE
+from OpenApiLibCore.models.oas_models import (
+ ArraySchema,
ObjectSchema,
OpenApiObject,
OperationObject,
ParameterObject,
+ ResolvedSchemaObjectTypes,
UnionTypeSchema,
)
-from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
-from OpenApiLibCore.protocols import GetDtoClassType, GetIdPropertyNameType
-from OpenApiLibCore.request_data import RequestData
-from OpenApiLibCore.value_utils import IGNORE
-
-from .body_data_generation import (
- get_json_data_for_dto_class as _get_json_data_for_dto_class,
+from OpenApiLibCore.models.request_data import RequestData
+from OpenApiLibCore.models.resource_relations import (
+ PropertyValueConstraint,
+ ResourceRelation,
)
+from OpenApiLibCore.protocols import RelationsMappingType
+from OpenApiLibCore.utils.parameter_utils import get_safe_name_for_oas_name
def get_request_data(
path: str,
method: str,
- get_dto_class: GetDtoClassType,
- get_id_property_name: GetIdPropertyNameType,
openapi_spec: OpenApiObject,
) -> RequestData:
method = method.lower()
- dto_cls_name = get_dto_cls_name(path=path, method=method)
+ mapping_cls_name = get_mapping_cls_name(path=path, method=method)
# The path can contain already resolved Ids that have to be matched
# against the parametrized paths in the paths section.
spec_path = _path_functions.get_parametrized_path(
path=path, openapi_spec=openapi_spec
)
- dto_class = get_dto_class(path=spec_path, method=method)
try:
path_item = openapi_spec.paths[spec_path]
operation_spec: OperationObject | None = getattr(path_item, method)
if operation_spec is None:
raise AttributeError
+ relations_mapping = operation_spec.relations_mapping
except AttributeError:
logger.info(
f"method '{method}' not supported on '{spec_path}, using empty spec."
)
operation_spec = OperationObject(operationId="")
+ relations_mapping = None
parameters, params, headers = get_request_parameters(
- dto_class=dto_class, method_spec=operation_spec
+ relations_mapping=relations_mapping, method_spec=operation_spec
)
if operation_spec.requestBody is None:
- dto_instance = _get_dto_instance_for_empty_body(
- dto_class=dto_class,
- dto_cls_name=dto_cls_name,
+ relations_mapping = _get_mapping_dataclass_for_empty_body(
+ relations_mapping=relations_mapping,
+ mapping_cls_name=mapping_cls_name,
method_spec=operation_spec,
)
return RequestData(
- dto=dto_instance,
+ valid_data=None,
+ relations_mapping=relations_mapping,
parameters=parameters,
params=params,
headers=headers,
@@ -85,92 +82,113 @@ def get_request_data(
f"No supported content schema found: {operation_spec.requestBody.content}"
)
- headers.update({"content-type": operation_spec.requestBody.mime_type})
-
- if isinstance(body_schema, UnionTypeSchema):
- resolved_schemas = body_schema.resolved_schemas
- body_schema = choice(resolved_schemas)
-
- if not isinstance(body_schema, ObjectSchema):
- raise ValueError(f"Selected schema is not an object schema: {body_schema}")
+ if operation_spec.requestBody.mime_type: # pragma: no branch
+ if "content-type" in headers: # pragma: no cover
+ key_value = "content-type"
+ else:
+ key_value = "Content-Type"
+ headers.update({key_value: operation_spec.requestBody.mime_type})
- dto_data = _get_json_data_for_dto_class(
- schema=body_schema,
- dto_class=dto_class,
- get_id_property_name=get_id_property_name,
- operation_id=operation_spec.operationId,
+ valid_data, schema_used_for_data_generation = body_schema.get_valid_value(
+ operation_id=operation_spec.operationId
)
- dto_instance = _get_dto_instance_from_dto_data(
- object_schema=body_schema,
- dto_class=dto_class,
- dto_data=dto_data,
+
+ relations_mapping = _get_mapping_dataclass_from_valid_data(
+ schema=schema_used_for_data_generation,
+ relations_mapping=relations_mapping,
+ valid_data=valid_data,
method_spec=operation_spec,
- dto_cls_name=dto_cls_name,
+ mapping_cls_name=mapping_cls_name,
)
return RequestData(
- dto=dto_instance,
- body_schema=body_schema,
+ valid_data=valid_data,
+ relations_mapping=relations_mapping,
+ body_schema=schema_used_for_data_generation,
parameters=parameters,
params=params,
headers=headers,
)
-def _get_dto_instance_for_empty_body(
- dto_class: type[Dto],
- dto_cls_name: str,
+def _get_mapping_dataclass_for_empty_body(
+ relations_mapping: RelationsMappingType | None,
+ mapping_cls_name: str,
method_spec: OperationObject,
-) -> Dto:
- if dto_class == DefaultDto:
- dto_instance: Dto = DefaultDto()
- else:
- cls_name = method_spec.operationId if method_spec.operationId else dto_cls_name
- dto_class = make_dataclass(
- cls_name=cls_name,
- fields=[],
- bases=(dto_class,),
- )
- dto_instance = dto_class()
- return dto_instance
+) -> RelationsMappingType:
+ cls_name = method_spec.operationId if method_spec.operationId else mapping_cls_name
+ base = relations_mapping if relations_mapping else RelationsMapping
+ mapping_class = make_dataclass(
+ cls_name=cls_name,
+ fields=[],
+ bases=(base,),
+ )
+ return mapping_class
-def _get_dto_instance_from_dto_data(
- object_schema: ObjectSchema,
- dto_class: type[Dto],
- dto_data: JSON,
+def _get_mapping_dataclass_from_valid_data(
+ schema: ResolvedSchemaObjectTypes,
+ relations_mapping: RelationsMappingType | None,
+ valid_data: JSON,
method_spec: OperationObject,
- dto_cls_name: str,
-) -> Dto:
- if not isinstance(dto_data, (dict, list)):
- return DefaultDto()
+ mapping_cls_name: str,
+) -> RelationsMappingType:
+ if not isinstance(schema, (ObjectSchema, ArraySchema)):
+ return _get_mapping_dataclass_for_empty_body(
+ relations_mapping=relations_mapping,
+ mapping_cls_name=mapping_cls_name,
+ method_spec=method_spec,
+ )
- if isinstance(dto_data, list):
- raise NotImplementedError
+ if isinstance(schema, ArraySchema):
+ if not valid_data or not isinstance(valid_data, list):
+ return _get_mapping_dataclass_for_empty_body(
+ relations_mapping=relations_mapping,
+ mapping_cls_name=mapping_cls_name,
+ method_spec=method_spec,
+ )
+ first_item_data = valid_data[0]
+ item_object_schema = schema.items
+
+ if isinstance(item_object_schema, UnionTypeSchema):
+ resolved_schemas = item_object_schema.resolved_schemas
+ for resolved_schema in resolved_schemas:
+ matched_schema = resolved_schema
+ if isinstance(first_item_data, resolved_schema.python_type):
+ break
+ else:
+ matched_schema = item_object_schema
+
+ mapping_dataclass = _get_mapping_dataclass_from_valid_data(
+ schema=matched_schema,
+ relations_mapping=relations_mapping,
+ valid_data=first_item_data,
+ method_spec=method_spec,
+ mapping_cls_name=mapping_cls_name,
+ )
+ return mapping_dataclass
- fields = get_fields_from_dto_data(object_schema, dto_data)
- cls_name = method_spec.operationId if method_spec.operationId else dto_cls_name
- dto_class_ = make_dataclass(
+ assert isinstance(valid_data, dict), (
+ "Data consistency error: schema is of type ObjectSchema but valid_data is not a dict."
+ )
+ fields = get_dataclass_fields(object_schema=schema, valid_data=valid_data)
+ cls_name = method_spec.operationId if method_spec.operationId else mapping_cls_name
+ base = relations_mapping if relations_mapping else RelationsMapping
+ mapping_dataclass = make_dataclass(
cls_name=cls_name,
fields=fields,
- bases=(dto_class,),
+ bases=(base,),
)
- # dto_data = {get_safe_key(key): value for key, value in dto_data.items()}
- dto_data = {
- get_safe_name_for_oas_name(key): value for key, value in dto_data.items()
- }
- return cast(Dto, dto_class_(**dto_data))
+ return mapping_dataclass
-def get_fields_from_dto_data(
- object_schema: ObjectSchema, dto_data: dict[str, JSON]
+def get_dataclass_fields(
+ object_schema: ObjectSchema, valid_data: dict[str, JSON]
) -> list[tuple[str, type[object], Field[object]]]:
- """Get a dataclasses fields list based on the content_schema and dto_data."""
+ """Get a dataclasses fields list based on the object_schema and valid_data."""
fields: list[tuple[str, type[object], Field[object]]] = []
- for key, value in dto_data.items():
- # safe_key = get_safe_key(key)
+ for key, value in valid_data.items():
safe_key = get_safe_name_for_oas_name(key)
- # metadata = {"original_property_name": key}
if key in object_schema.required:
# The fields list is used to create a dataclass, so non-default fields
# must go before fields with a default
@@ -182,7 +200,7 @@ def get_fields_from_dto_data(
return fields
-def get_dto_cls_name(path: str, method: str) -> str:
+def get_mapping_cls_name(path: str, method: str) -> str:
method = method.capitalize()
path = path.translate({ord(i): None for i in "{}"})
path_parts = path.split("/")
@@ -192,11 +210,13 @@ def get_dto_cls_name(path: str, method: str) -> str:
def get_request_parameters(
- dto_class: Dto | type[Dto], method_spec: OperationObject
+ relations_mapping: RelationsMappingType | None, method_spec: OperationObject
) -> tuple[list[ParameterObject], dict[str, Any], dict[str, str]]:
"""Get the methods parameter spec and params and headers with valid data."""
parameters = method_spec.parameters if method_spec.parameters else []
- parameter_relations = dto_class.get_parameter_relations()
+ parameter_relations = (
+ relations_mapping.get_parameter_relations() if relations_mapping else []
+ )
query_params = [p for p in parameters if p.in_ == "query"]
header_params = [p for p in parameters if p.in_ == "header"]
params = get_parameter_data(query_params, parameter_relations)
@@ -229,7 +249,7 @@ def get_parameter_data(
continue
if parameter.schema_ is None:
- continue
- value = parameter.schema_.get_valid_value()
+ continue # pragma: no cover
+ value = parameter.schema_.get_valid_value()[0]
result[parameter_name] = value
return result
diff --git a/src/OpenApiLibCore/data_invalidation.py b/src/OpenApiLibCore/data_generation/data_invalidation.py
similarity index 61%
rename from src/OpenApiLibCore/data_invalidation.py
rename to src/OpenApiLibCore/data_generation/data_invalidation.py
index 63fa745..e46107e 100644
--- a/src/OpenApiLibCore/data_invalidation.py
+++ b/src/OpenApiLibCore/data_generation/data_invalidation.py
@@ -5,80 +5,149 @@
from copy import deepcopy
from random import choice
-from typing import Any
+from typing import Any, Literal, overload
from requests import Response
from robot.api import logger
from robot.libraries.BuiltIn import BuiltIn
from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.dto_base import (
+from OpenApiLibCore.data_relations.relations_base import RelationsMapping
+from OpenApiLibCore.models import IGNORE
+from OpenApiLibCore.models.oas_models import (
+ ArraySchema,
+ ObjectSchema,
+ ParameterObject,
+ UnionTypeSchema,
+)
+from OpenApiLibCore.models.request_data import RequestData
+from OpenApiLibCore.models.resource_relations import (
NOT_SET,
- Dto,
IdReference,
PropertyValueConstraint,
UniquePropertyValueConstraint,
)
-from OpenApiLibCore.models import ParameterObject, UnionTypeSchema
-from OpenApiLibCore.request_data import RequestData
-from OpenApiLibCore.value_utils import IGNORE, get_invalid_value
run_keyword = BuiltIn().run_keyword
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_json_data_with_conflict"], *args: object
+) -> dict[str, JSON]: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["ensure_in_use"], *args: object
+) -> None: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_request_data"], *args: str
+) -> RequestData: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["authorized_request"], *args: object
+) -> Response: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
def get_invalid_body_data(
url: str,
method: str,
status_code: int,
request_data: RequestData,
- invalid_property_default_response: int,
-) -> dict[str, Any]:
+ invalid_data_default_response: int,
+) -> JSON:
method = method.lower()
- data_relations = request_data.dto.get_body_relations_for_error_code(status_code)
+ data_relations = request_data.relations_mapping.get_body_relations_for_error_code(
+ status_code
+ )
if not data_relations:
if request_data.body_schema is None:
raise ValueError(
"Failed to invalidate: request_data does not contain a body_schema."
)
- json_data = request_data.dto.get_invalidated_data(
- schema=request_data.body_schema,
+
+ if not isinstance(request_data.body_schema, (ArraySchema, ObjectSchema)):
+ raise NotImplementedError("primitive types not supported for body data.")
+
+ if isinstance(request_data.body_schema, ArraySchema):
+ if not isinstance(request_data.valid_data, list):
+ raise ValueError("Type of valid_data does not match body_schema type.")
+ invalid_item_data: list[JSON] = request_data.body_schema.get_invalid_data(
+ valid_data=request_data.valid_data,
+ status_code=status_code,
+ invalid_property_default_code=invalid_data_default_response,
+ )
+ return [invalid_item_data]
+
+ if not isinstance(request_data.valid_data, dict):
+ raise ValueError("Type of valid_data does not match body_schema type.")
+ json_data = request_data.body_schema.get_invalid_data(
+ valid_data=request_data.valid_data,
status_code=status_code,
- invalid_property_default_code=invalid_property_default_response,
+ invalid_property_default_code=invalid_data_default_response,
)
return json_data
+
resource_relation = choice(data_relations)
if isinstance(resource_relation, UniquePropertyValueConstraint):
- json_data = run_keyword(
+ return _run_keyword(
"get_json_data_with_conflict",
url,
method,
- request_data.dto,
+ request_data.valid_data,
+ request_data.relations_mapping,
status_code,
)
- elif isinstance(resource_relation, IdReference):
- run_keyword("ensure_in_use", url, resource_relation)
- json_data = request_data.dto.as_dict()
- else:
- if request_data.body_schema is None:
- raise ValueError(
- "Failed to invalidate: request_data does not contain a body_schema."
- )
- json_data = request_data.dto.get_invalidated_data(
- schema=request_data.body_schema,
+ if isinstance(resource_relation, IdReference):
+ _run_keyword("ensure_in_use", url, resource_relation)
+ return request_data.valid_data
+
+ if request_data.body_schema is None:
+ raise ValueError(
+ "Failed to invalidate: request_data does not contain a body_schema."
+ )
+ if not isinstance(request_data.body_schema, (ArraySchema, ObjectSchema)):
+ raise NotImplementedError("primitive types not supported for body data.")
+
+ if isinstance(request_data.body_schema, ArraySchema):
+ if not isinstance(request_data.valid_data, list):
+ raise ValueError("Type of valid_data does not match body_schema type.")
+ invalid_item_data = request_data.body_schema.get_invalid_data(
+ valid_data=request_data.valid_data,
status_code=status_code,
- invalid_property_default_code=invalid_property_default_response,
+ invalid_property_default_code=invalid_data_default_response,
)
- return json_data
+ return [invalid_item_data]
+
+ if not isinstance(request_data.valid_data, dict):
+ raise ValueError("Type of valid_data does not match body_schema type.")
+ return request_data.body_schema.get_invalid_data(
+ valid_data=request_data.valid_data,
+ status_code=status_code,
+ invalid_property_default_code=invalid_data_default_response,
+ )
def get_invalidated_parameters(
- status_code: int, request_data: RequestData, invalid_property_default_response: int
-) -> tuple[dict[str, JSON], dict[str, JSON]]:
+ status_code: int, request_data: RequestData, invalid_data_default_response: int
+) -> tuple[dict[str, JSON], dict[str, str]]:
if not request_data.parameters:
raise ValueError("No params or headers to invalidate.")
# ensure the status_code can be triggered
- relations = request_data.dto.get_parameter_relations_for_error_code(status_code)
+ relations = request_data.relations_mapping.get_parameter_relations_for_error_code(
+ status_code
+ )
relations_for_status_code = [
r
for r in relations
@@ -92,14 +161,14 @@ def get_invalidated_parameters(
}
relation_property_names = {r.property_name for r in relations_for_status_code}
if not relation_property_names:
- if status_code != invalid_property_default_response:
+ if status_code != invalid_data_default_response:
raise ValueError(f"No relations to cause status_code {status_code} found.")
# ensure we're not modifying mutable properties
params = deepcopy(request_data.params)
headers = deepcopy(request_data.headers)
- if status_code == invalid_property_default_response:
+ if status_code == invalid_data_default_response:
# take the params and headers that can be invalidated based on data type
# and expand the set with properties that can be invalided by relations
parameter_names = set(request_data.params_that_can_be_invalidated).union(
@@ -114,8 +183,8 @@ def get_invalidated_parameters(
# non-default status_codes can only be the result of a Relation
parameter_names = relation_property_names
- # Dto mappings may contain generic mappings for properties that are not present
- # in this specific schema
+ # Relation mappings may contain generic mappings for properties that are
+ # not present in this specific schema
request_data_parameter_names = [p.name for p in request_data.parameters]
additional_relation_property_names = {
n for n in relation_property_names if n not in request_data_parameter_names
@@ -164,7 +233,7 @@ def get_invalidated_parameters(
except ValueError:
invalid_value_for_error_code = NOT_SET
- # get the constraint values if available for the chosen parameter
+ # get the constrained values if available for the chosen parameter
try:
[values_from_constraint] = [
r.values
@@ -197,19 +266,17 @@ def get_invalidated_parameters(
raise ValueError(f"No schema defined for parameter: {parameter_data}.")
if isinstance(value_schema, UnionTypeSchema):
- # FIXME: extra handling may be needed in case of values_from_constraint
value_schema = choice(value_schema.resolved_schemas)
- invalid_value = get_invalid_value(
- value_schema=value_schema,
- current_value=valid_value,
+ invalid_value = value_schema.get_invalid_value(
+ valid_value=valid_value, # type: ignore[arg-type]
values_from_constraint=values_from_constraint,
)
logger.debug(f"{parameter_to_invalidate} changed to {invalid_value}")
# update the params / headers and return
if parameter_to_invalidate in params.keys():
- params[parameter_to_invalidate] = invalid_value
+ params[parameter_to_invalidate] = invalid_value # pyright: ignore[reportArgumentType]
else:
headers[parameter_to_invalidate] = str(invalid_value)
return params, headers
@@ -218,10 +285,10 @@ def get_invalidated_parameters(
def ensure_parameter_in_parameters(
parameter_to_invalidate: str,
params: dict[str, JSON],
- headers: dict[str, JSON],
+ headers: dict[str, str],
parameter_data: ParameterObject,
values_from_constraint: list[JSON],
-) -> tuple[dict[str, JSON], dict[str, JSON]]:
+) -> tuple[dict[str, JSON], dict[str, str]]:
"""
Returns the params, headers tuple with parameter_to_invalidate with a valid
value to params or headers if not originally present.
@@ -239,7 +306,7 @@ def ensure_parameter_in_parameters(
if isinstance(value_schema, UnionTypeSchema):
value_schema = choice(value_schema.resolved_schemas)
- valid_value = value_schema.get_valid_value()
+ valid_value = value_schema.get_valid_value()[0]
if (
parameter_data.in_ == "query"
and parameter_to_invalidate not in params.keys()
@@ -254,12 +321,18 @@ def ensure_parameter_in_parameters(
def get_json_data_with_conflict(
- url: str, base_url: str, method: str, dto: Dto, conflict_status_code: int
+ url: str,
+ base_url: str,
+ method: str,
+ json_data: dict[str, JSON],
+ relations_mapping: type[RelationsMapping],
+ conflict_status_code: int,
) -> dict[str, Any]:
method = method.lower()
- json_data = dto.as_dict()
unique_property_value_constraints = [
- r for r in dto.get_relations() if isinstance(r, UniquePropertyValueConstraint)
+ r
+ for r in relations_mapping.get_relations()
+ if isinstance(r, UniquePropertyValueConstraint)
]
for relation in unique_property_value_constraints:
json_data[relation.property_name] = relation.value
@@ -267,21 +340,22 @@ def get_json_data_with_conflict(
if method in ["patch", "put"]:
post_url_parts = url.split("/")[:-1]
post_url = "/".join(post_url_parts)
- # the PATCH or PUT may use a different dto than required for POST
- # so a valid POST dto must be constructed
+ # the PATCH or PUT may use a different relations_mapping than required for
+ # POST so valid POST data must be constructed
path = post_url.replace(base_url, "")
- request_data: RequestData = run_keyword("get_request_data", path, "post")
- post_json = request_data.dto.as_dict()
- for key in post_json.keys():
- if key in json_data:
- post_json[key] = json_data.get(key)
+ request_data = _run_keyword("get_request_data", path, "post")
+ post_json = request_data.valid_data
+ if isinstance(post_json, dict):
+ for key in post_json.keys():
+ if key in json_data:
+ post_json[key] = json_data.get(key)
else:
post_url = url
post_json = json_data
path = post_url.replace(base_url, "")
- request_data = run_keyword("get_request_data", path, "post")
+ request_data = _run_keyword("get_request_data", path, "post")
- response: Response = run_keyword(
+ response = _run_keyword(
"authorized_request",
post_url,
"post",
@@ -295,5 +369,6 @@ def get_json_data_with_conflict(
)
return json_data
raise ValueError(
- f"No UniquePropertyValueConstraint in the get_relations list on dto {dto}."
+ f"No UniquePropertyValueConstraint in the get_relations list on "
+ f"relations_mapping {relations_mapping}."
)
diff --git a/src/OpenApiLibCore/localized_faker.py b/src/OpenApiLibCore/data_generation/localized_faker.py
similarity index 100%
rename from src/OpenApiLibCore/localized_faker.py
rename to src/OpenApiLibCore/data_generation/localized_faker.py
diff --git a/src/OpenApiLibCore/data_generation/value_utils.py b/src/OpenApiLibCore/data_generation/value_utils.py
new file mode 100644
index 0000000..81dc28c
--- /dev/null
+++ b/src/OpenApiLibCore/data_generation/value_utils.py
@@ -0,0 +1,39 @@
+"""Utility module with functions to handle OpenAPI value types and restrictions."""
+
+
+def json_type_name_of_python_type(python_type: type) -> str:
+ """Return the JSON type name for supported Python types."""
+ if python_type == str:
+ return "string"
+ if python_type == bool:
+ return "boolean"
+ if python_type == int:
+ return "integer"
+ if python_type == float:
+ return "number"
+ if python_type == list:
+ return "array"
+ if python_type == dict:
+ return "object"
+ if python_type == type(None):
+ return "null"
+ raise ValueError(f"No json type mapping for Python type {python_type} available.")
+
+
+def python_type_by_json_type_name(type_name: str) -> type:
+ """Return the Python type based on the JSON type name."""
+ if type_name == "string":
+ return str
+ if type_name == "boolean":
+ return bool
+ if type_name == "integer":
+ return int
+ if type_name == "number":
+ return float
+ if type_name == "array":
+ return list
+ if type_name == "object":
+ return dict
+ if type_name == "null":
+ return type(None)
+ raise ValueError(f"No Python type mapping for JSON type '{type_name}' available.")
diff --git a/src/OpenApiLibCore/data_relations/__init__.py b/src/OpenApiLibCore/data_relations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/OpenApiLibCore/data_relations/relations_base.py b/src/OpenApiLibCore/data_relations/relations_base.py
new file mode 100644
index 0000000..3a4c071
--- /dev/null
+++ b/src/OpenApiLibCore/data_relations/relations_base.py
@@ -0,0 +1,142 @@
+"""
+Module holding the (base) classes that can be used by the user of the OpenApiLibCore
+to implement custom mappings for dependencies between resources in the API under
+test and constraints / restrictions on properties of the resources.
+"""
+
+from abc import ABC
+from dataclasses import dataclass
+from importlib import import_module
+from typing import Callable
+
+from robot.api import logger
+
+from OpenApiLibCore.models.resource_relations import (
+ NOT_SET,
+ PathPropertiesConstraint,
+ ResourceRelation,
+)
+from OpenApiLibCore.protocols import (
+ IGetIdPropertyName,
+ RelationsMappingType,
+)
+from OpenApiLibCore.utils.id_mapping import dummy_transformer
+
+
+@dataclass
+class RelationsMapping(ABC):
+ """Base class for the RelationsMapping classes."""
+
+ @staticmethod
+ def get_path_relations() -> list[PathPropertiesConstraint]:
+ """Return the list of path-related Relations."""
+ return []
+
+ @staticmethod
+ def get_parameter_relations() -> list[ResourceRelation]:
+ """Return the list of Relations for the header and query parameters."""
+ return []
+
+ @classmethod
+ def get_parameter_relations_for_error_code(
+ cls, error_code: int
+ ) -> list[ResourceRelation]:
+ """Return the list of Relations associated with the given error_code."""
+ relations: list[ResourceRelation] = [
+ r
+ for r in cls.get_parameter_relations()
+ if r.error_code == error_code
+ or (
+ getattr(r, "invalid_value_error_code", None) == error_code
+ and getattr(r, "invalid_value", None) != NOT_SET
+ )
+ ]
+ return relations
+
+ @staticmethod
+ def get_relations() -> list[ResourceRelation]:
+ """Return the list of Relations for the (json) body."""
+ return []
+
+ @classmethod
+ def get_body_relations_for_error_code(
+ cls, error_code: int
+ ) -> list[ResourceRelation]:
+ """
+ Return the list of Relations associated with the given error_code that are
+ applicable to the body / payload of the request.
+ """
+ relations: list[ResourceRelation] = [
+ r
+ for r in cls.get_relations()
+ if r.error_code == error_code
+ or (
+ getattr(r, "invalid_value_error_code", None) == error_code
+ and getattr(r, "invalid_value", None) != NOT_SET
+ )
+ ]
+ return relations
+
+
+def get_relations_mapping_dict(
+ mappings_module_name: str,
+) -> dict[tuple[str, str], RelationsMappingType]:
+ try:
+ mappings_module = import_module(mappings_module_name)
+ return mappings_module.RELATIONS_MAPPING # type: ignore[no-any-return]
+ except (ImportError, AttributeError, ValueError) as exception:
+ if mappings_module_name != "no mapping":
+ logger.error(f"RELATIONS_MAPPING was not imported: {exception}")
+ return {}
+
+
+def get_path_mapping_dict(
+ mappings_module_name: str,
+) -> dict[str, RelationsMappingType]:
+ try:
+ mappings_module = import_module(mappings_module_name)
+ return mappings_module.PATH_MAPPING # type: ignore[no-any-return]
+ except (ImportError, AttributeError, ValueError) as exception:
+ if mappings_module_name != "no mapping":
+ logger.error(f"PATH_MAPPING was not imported: {exception}")
+ return {}
+
+
+def get_id_property_name(
+ mappings_module_name: str, default_id_property_name: str
+) -> IGetIdPropertyName:
+ return GetIdPropertyName(
+ mappings_module_name=mappings_module_name,
+ default_id_property_name=default_id_property_name,
+ )
+
+
+class GetIdPropertyName:
+ """
+ Callable class to return the name of the property that uniquely identifies
+ the resource from user-implemented mappings file.
+ """
+
+ def __init__(
+ self, mappings_module_name: str, default_id_property_name: str
+ ) -> None:
+ self.default_id_property_name = default_id_property_name
+ try:
+ mappings_module = import_module(mappings_module_name)
+ self.id_mapping: dict[
+ str,
+ str | tuple[str, Callable[[str], str]],
+ ] = mappings_module.ID_MAPPING
+ except (ImportError, AttributeError, ValueError) as exception:
+ if mappings_module_name != "no mapping":
+ logger.error(f"ID_MAPPING was not imported: {exception}")
+ self.id_mapping = {}
+
+ def __call__(self, path: str) -> tuple[str, Callable[[str], str]]:
+ try:
+ value_or_mapping = self.id_mapping[path]
+ if isinstance(value_or_mapping, str):
+ return (value_or_mapping, dummy_transformer)
+ return value_or_mapping
+ except KeyError:
+ return (self.default_id_property_name, dummy_transformer)
diff --git a/src/OpenApiLibCore/dto_base.py b/src/OpenApiLibCore/dto_base.py
deleted file mode 100644
index b646103..0000000
--- a/src/OpenApiLibCore/dto_base.py
+++ /dev/null
@@ -1,260 +0,0 @@
-"""
-Module holding the (base) classes that can be used by the user of the OpenApiLibCore
-to implement custom mappings for dependencies between resources in the API under
-test and constraints / restrictions on properties of the resources.
-"""
-
-from abc import ABC
-from dataclasses import dataclass, fields
-from random import choice, shuffle
-from typing import Any
-from uuid import uuid4
-
-from robot.api import logger
-
-from OpenApiLibCore import value_utils
-from OpenApiLibCore.models import NullSchema, ObjectSchema, UnionTypeSchema
-from OpenApiLibCore.parameter_utils import get_oas_name_from_safe_name
-
-NOT_SET = object()
-SENTINEL = object()
-
-
-class ResourceRelation(ABC):
- """ABC for all resource relations or restrictions within the API."""
-
- property_name: str
- error_code: int
-
-
-@dataclass
-class PathPropertiesConstraint(ResourceRelation):
- """The value to be used as the ``path`` for related requests."""
-
- path: str
- property_name: str = "id"
- invalid_value: Any = NOT_SET
- invalid_value_error_code: int = 422
- error_code: int = 404
-
-
-@dataclass
-class PropertyValueConstraint(ResourceRelation):
- """The allowed values for property_name."""
-
- property_name: str
- values: list[Any]
- invalid_value: Any = NOT_SET
- invalid_value_error_code: int = 422
- error_code: int = 422
- treat_as_mandatory: bool = False
-
-
-@dataclass
-class IdDependency(ResourceRelation):
- """The path where a valid id for the property_name can be gotten (using GET)."""
-
- property_name: str
- get_path: str
- operation_id: str = ""
- error_code: int = 422
-
-
-@dataclass
-class IdReference(ResourceRelation):
- """The path where a resource that needs this resource's id can be created (using POST)."""
-
- property_name: str
- post_path: str
- error_code: int = 422
-
-
-@dataclass
-class UniquePropertyValueConstraint(ResourceRelation):
- """The value of the property must be unique within the resource scope."""
-
- property_name: str
- value: Any
- error_code: int = 422
-
-
-@dataclass
-class Dto(ABC):
- """Base class for the Dto class."""
-
- @staticmethod
- def get_path_relations() -> list[PathPropertiesConstraint]:
- """Return the list of Relations for the header and query parameters."""
- return []
-
- def get_path_relations_for_error_code(
- self, error_code: int
- ) -> list[PathPropertiesConstraint]:
- """Return the list of Relations associated with the given error_code."""
- relations: list[PathPropertiesConstraint] = [
- r
- for r in self.get_path_relations()
- if r.error_code == error_code
- or (
- getattr(r, "invalid_value_error_code", None) == error_code
- and getattr(r, "invalid_value", None) != NOT_SET
- )
- ]
- return relations
-
- @staticmethod
- def get_parameter_relations() -> list[ResourceRelation]:
- """Return the list of Relations for the header and query parameters."""
- return []
-
- def get_parameter_relations_for_error_code(
- self, error_code: int
- ) -> list[ResourceRelation]:
- """Return the list of Relations associated with the given error_code."""
- relations: list[ResourceRelation] = [
- r
- for r in self.get_parameter_relations()
- if r.error_code == error_code
- or (
- getattr(r, "invalid_value_error_code", None) == error_code
- and getattr(r, "invalid_value", None) != NOT_SET
- )
- ]
- return relations
-
- @staticmethod
- def get_relations() -> list[ResourceRelation]:
- """Return the list of Relations for the (json) body."""
- return []
-
- def get_body_relations_for_error_code(
- self, error_code: int
- ) -> list[ResourceRelation]:
- """
- Return the list of Relations associated with the given error_code that are
- applicable to the body / payload of the request.
- """
- relations: list[ResourceRelation] = [
- r
- for r in self.get_relations()
- if r.error_code == error_code
- or (
- getattr(r, "invalid_value_error_code", None) == error_code
- and getattr(r, "invalid_value", None) != NOT_SET
- )
- ]
- return relations
-
- def get_invalidated_data(
- self,
- schema: ObjectSchema,
- status_code: int,
- invalid_property_default_code: int,
- ) -> dict[str, Any]:
- """Return a data set with one of the properties set to an invalid value or type."""
- properties: dict[str, Any] = self.as_dict()
-
- relations = self.get_body_relations_for_error_code(error_code=status_code)
- property_names = [r.property_name for r in relations]
- if status_code == invalid_property_default_code:
- # add all properties defined in the schema, including optional properties
- property_names.extend((schema.properties.root.keys())) # type: ignore[union-attr]
- if not property_names:
- raise ValueError(
- f"No property can be invalidated to cause status_code {status_code}"
- )
- # Remove duplicates, then shuffle the property_names so different properties on
- # the Dto are invalidated when rerunning the test.
- shuffle(list(set(property_names)))
- for property_name in property_names:
- # if possible, invalidate a constraint but send otherwise valid data
- id_dependencies = [
- r
- for r in relations
- if isinstance(r, IdDependency) and r.property_name == property_name
- ]
- if id_dependencies:
- invalid_id = uuid4().hex
- logger.debug(
- f"Breaking IdDependency for status_code {status_code}: setting "
- f"{property_name} to {invalid_id}"
- )
- properties[property_name] = invalid_id
- return properties
-
- invalid_value_from_constraint = [
- r.invalid_value
- for r in relations
- if isinstance(r, PropertyValueConstraint)
- and r.property_name == property_name
- and r.invalid_value_error_code == status_code
- ]
- if (
- invalid_value_from_constraint
- and invalid_value_from_constraint[0] is not NOT_SET
- ):
- properties[property_name] = invalid_value_from_constraint[0]
- logger.debug(
- f"Using invalid_value {invalid_value_from_constraint[0]} to "
- f"invalidate property {property_name}"
- )
- return properties
-
- value_schema = schema.properties.root[property_name] # type: ignore[union-attr]
- if isinstance(value_schema, UnionTypeSchema):
- # Filter "type": "null" from the possible types since this indicates an
- # optional / nullable property that can only be invalidated by sending
- # invalid data of a non-null type
- non_null_schemas = [
- s
- for s in value_schema.resolved_schemas
- if not isinstance(s, NullSchema)
- ]
- value_schema = choice(non_null_schemas)
-
- # there may not be a current_value when invalidating an optional property
- current_value = properties.get(property_name, SENTINEL)
- if current_value is SENTINEL:
- # the current_value isn't very relevant as long as the type is correct
- # so no logic to handle Relations / objects / arrays here
- property_type = value_schema.type
- if property_type == "object":
- current_value = {}
- elif property_type == "array":
- current_value = []
- else:
- current_value = value_schema.get_valid_value()
-
- values_from_constraint = [
- r.values[0]
- for r in relations
- if isinstance(r, PropertyValueConstraint)
- and r.property_name == property_name
- ]
-
- invalid_value = value_utils.get_invalid_value(
- value_schema=value_schema,
- current_value=current_value,
- values_from_constraint=values_from_constraint,
- )
- properties[property_name] = invalid_value
- logger.debug(
- f"Property {property_name} changed to {invalid_value!r} (received from "
- f"get_invalid_value)"
- )
- return properties
- logger.warn("get_invalidated_data returned unchanged properties")
- return properties # pragma: no cover
-
- def as_dict(self) -> dict[Any, Any]:
- """Return the dict representation of the Dto."""
- result = {}
-
- for field in fields(self):
- field_name = field.name
- if field_name not in self.__dict__:
- continue
- original_name = get_oas_name_from_safe_name(field_name)
- result[original_name] = getattr(self, field_name)
-
- return result
diff --git a/src/OpenApiLibCore/dto_utils.py b/src/OpenApiLibCore/dto_utils.py
deleted file mode 100644
index 4f8c8c9..0000000
--- a/src/OpenApiLibCore/dto_utils.py
+++ /dev/null
@@ -1,125 +0,0 @@
-"""Module for helper methods and classes used by the openapi_executors module."""
-
-from dataclasses import dataclass
-from importlib import import_module
-from typing import Any, Callable, Type, overload
-
-from robot.api import logger
-
-from OpenApiLibCore.dto_base import Dto
-from OpenApiLibCore.protocols import (
- GetDtoClassType,
- GetIdPropertyNameType,
- GetPathDtoClassType,
-)
-
-
-@dataclass
-class _DefaultIdPropertyName:
- id_property_name: str = "id"
-
-
-DEFAULT_ID_PROPERTY_NAME = _DefaultIdPropertyName()
-
-
-@dataclass
-class DefaultDto(Dto):
- """A default Dto that can be instantiated."""
-
-
-def get_dto_class(mappings_module_name: str) -> GetDtoClassType:
- return GetDtoClass(mappings_module_name=mappings_module_name)
-
-
-class GetDtoClass:
- """Callable class to return Dtos from user-implemented mappings file."""
-
- def __init__(self, mappings_module_name: str) -> None:
- try:
- mappings_module = import_module(mappings_module_name)
- self.dto_mapping: dict[tuple[str, str], Type[Dto]] = (
- mappings_module.DTO_MAPPING
- )
- except (ImportError, AttributeError, ValueError) as exception:
- if mappings_module_name != "no mapping":
- logger.error(f"DTO_MAPPING was not imported: {exception}")
- self.dto_mapping = {}
-
- def __call__(self, path: str, method: str) -> Type[Dto]:
- try:
- return self.dto_mapping[(path, method.lower())]
- except KeyError:
- logger.debug(f"No Dto mapping for {path} {method}.")
- return DefaultDto
-
-
-def get_path_dto_class(mappings_module_name: str) -> GetPathDtoClassType:
- return GetPathDtoClass(mappings_module_name=mappings_module_name)
-
-
-class GetPathDtoClass:
- """Callable class to return Dtos from user-implemented mappings file."""
-
- def __init__(self, mappings_module_name: str) -> None:
- try:
- mappings_module = import_module(mappings_module_name)
- self.dto_mapping: dict[str, Type[Dto]] = mappings_module.PATH_MAPPING
- except (ImportError, AttributeError, ValueError) as exception:
- if mappings_module_name != "no mapping":
- logger.error(f"PATH_MAPPING was not imported: {exception}")
- self.dto_mapping = {}
-
- def __call__(self, path: str) -> Type[Dto]:
- try:
- return self.dto_mapping[path]
- except KeyError:
- logger.debug(f"No Dto mapping for {path}.")
- return DefaultDto
-
-
-def get_id_property_name(mappings_module_name: str) -> GetIdPropertyNameType:
- return GetIdPropertyName(mappings_module_name=mappings_module_name)
-
-
-class GetIdPropertyName:
- """
- Callable class to return the name of the property that uniquely identifies
- the resource from user-implemented mappings file.
- """
-
- def __init__(self, mappings_module_name: str) -> None:
- try:
- mappings_module = import_module(mappings_module_name)
- self.id_mapping: dict[
- str,
- str | tuple[str, Callable[[str], str] | Callable[[int], int]],
- ] = mappings_module.ID_MAPPING
- except (ImportError, AttributeError, ValueError) as exception:
- if mappings_module_name != "no mapping":
- logger.error(f"ID_MAPPING was not imported: {exception}")
- self.id_mapping = {}
-
- def __call__(
- self, path: str
- ) -> tuple[str, Callable[[str], str] | Callable[[int], int]]:
- try:
- value_or_mapping = self.id_mapping[path]
- if isinstance(value_or_mapping, str):
- return (value_or_mapping, dummy_transformer)
- return value_or_mapping
- except KeyError:
- default_id_name = DEFAULT_ID_PROPERTY_NAME.id_property_name
- logger.debug(f"No id mapping for {path} ('{default_id_name}' will be used)")
- return (default_id_name, dummy_transformer)
-
-
-@overload
-def dummy_transformer(valid_id: str) -> str: ... # pragma: no cover
-
-
-@overload
-def dummy_transformer(valid_id: int) -> int: ... # pragma: no cover
-
-
-def dummy_transformer(valid_id: Any) -> Any:
- return valid_id
diff --git a/src/OpenApiLibCore/keyword_logic/__init__.py b/src/OpenApiLibCore/keyword_logic/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/OpenApiLibCore/path_functions.py b/src/OpenApiLibCore/keyword_logic/path_functions.py
similarity index 75%
rename from src/OpenApiLibCore/path_functions.py
rename to src/OpenApiLibCore/keyword_logic/path_functions.py
index d104d7b..13e5151 100644
--- a/src/OpenApiLibCore/path_functions.py
+++ b/src/OpenApiLibCore/keyword_logic/path_functions.py
@@ -3,18 +3,57 @@
import json as _json
from itertools import zip_longest
from random import choice
-from typing import Any
+from typing import Any, Literal, overload
from requests import Response
from robot.libraries.BuiltIn import BuiltIn
-from OpenApiLibCore.models import OpenApiObject
-from OpenApiLibCore.protocols import GetIdPropertyNameType, GetPathDtoClassType
-from OpenApiLibCore.request_data import RequestData
+from OpenApiLibCore.models import oas_models
+from OpenApiLibCore.models.request_data import RequestData
run_keyword = BuiltIn().run_keyword
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_valid_id_for_path"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_ids_from_url"], *args: str
+) -> list[str]: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_valid_url"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_parameterized_path_from_url"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_request_data"], *args: str
+) -> RequestData: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["authorized_request"], *args: object
+) -> Response: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
def match_parts(parts: list[str], spec_parts: list[str]) -> bool:
for part, spec_part in zip_longest(parts, spec_parts, fillvalue="Filler"):
if part == "Filler" or spec_part == "Filler":
@@ -24,14 +63,14 @@ def match_parts(parts: list[str], spec_parts: list[str]) -> bool:
return True
-def get_parametrized_path(path: str, openapi_spec: OpenApiObject) -> str:
+def get_parametrized_path(path: str, openapi_spec: oas_models.OpenApiObject) -> str:
path_parts = path.split("/")
# if the last part is empty, the path has a trailing `/` that
# should be ignored during matching
if path_parts[-1] == "":
_ = path_parts.pop(-1)
- spec_paths: list[str] = list(openapi_spec.paths.keys())
+ spec_paths = list(openapi_spec.paths.keys())
candidates: list[str] = []
@@ -63,19 +102,19 @@ def get_parametrized_path(path: str, openapi_spec: OpenApiObject) -> str:
def get_valid_url(
path: str,
base_url: str,
- get_path_dto_class: GetPathDtoClassType,
- openapi_spec: OpenApiObject,
+ openapi_spec: oas_models.OpenApiObject,
) -> str:
try:
# path can be partially resolved or provided by a PathPropertiesConstraint
parametrized_path = get_parametrized_path(path=path, openapi_spec=openapi_spec)
- _ = openapi_spec.paths[parametrized_path]
+ path_item = openapi_spec.paths[parametrized_path]
except KeyError:
raise ValueError(
f"{path} not found in paths section of the OpenAPI document."
) from None
- dto_class = get_path_dto_class(path=path)
- relations = dto_class.get_path_relations()
+
+ relations_mapping = path_item.relations_mapping
+ relations = relations_mapping.get_path_relations() if relations_mapping else []
paths = [p.path for p in relations]
if paths:
url = f"{base_url}{choice(paths)}"
@@ -85,9 +124,7 @@ def get_valid_url(
if part.startswith("{") and part.endswith("}"):
type_path_parts = path_parts[slice(index)]
type_path = "/".join(type_path_parts)
- existing_id: str | int | float = run_keyword(
- "get_valid_id_for_path", type_path
- )
+ existing_id = _run_keyword("get_valid_id_for_path", type_path)
path_parts[index] = str(existing_id)
resolved_path = "/".join(path_parts)
url = f"{base_url}{resolved_path}"
@@ -96,14 +133,14 @@ def get_valid_url(
def get_valid_id_for_path(
path: str,
- get_id_property_name: GetIdPropertyNameType,
-) -> str | int:
- url: str = run_keyword("get_valid_url", path)
+ openapi_spec: oas_models.OpenApiObject,
+) -> str:
+ url = _run_keyword("get_valid_url", path)
# Try to create a new resource to prevent conflicts caused by
# operations performed on the same resource by other test cases
- request_data: RequestData = run_keyword("get_request_data", path, "post")
+ request_data = _run_keyword("get_request_data", path, "post")
- response: Response = run_keyword(
+ response = _run_keyword(
"authorized_request",
url,
"post",
@@ -112,13 +149,14 @@ def get_valid_id_for_path(
request_data.get_required_properties_dict(),
)
- id_property, id_transformer = get_id_property_name(path=path)
+ path_item = openapi_spec.paths[path]
+ id_property, id_transformer = path_item.id_mapper
if not response.ok:
# If a new resource cannot be created using POST, try to retrieve a
# valid id using a GET request.
try:
- valid_id = choice(run_keyword("get_ids_from_url", url))
+ valid_id = choice(_run_keyword("get_ids_from_url", url))
return id_transformer(valid_id)
except Exception as exception:
raise AssertionError(
@@ -172,11 +210,11 @@ def get_valid_id_for_path(
def get_ids_from_url(
url: str,
- get_id_property_name: GetIdPropertyNameType,
+ openapi_spec: oas_models.OpenApiObject,
) -> list[str]:
- path: str = run_keyword("get_parameterized_path_from_url", url)
- request_data: RequestData = run_keyword("get_request_data", path, "get")
- response = run_keyword(
+ path = _run_keyword("get_parameterized_path_from_url", url)
+ request_data = _run_keyword("get_request_data", path, "get")
+ response = _run_keyword(
"authorized_request",
url,
"get",
@@ -187,11 +225,8 @@ def get_ids_from_url(
response_data: dict[str, Any] | list[dict[str, Any]] = response.json()
# determine the property name to use
- mapping = get_id_property_name(path=path)
- if isinstance(mapping, str):
- id_property = mapping
- else:
- id_property, _ = mapping
+ path_item = openapi_spec.paths[path]
+ id_property, _ = path_item.id_mapper
if isinstance(response_data, list):
valid_ids: list[str] = [item[id_property] for item in response_data]
diff --git a/src/OpenApiLibCore/path_invalidation.py b/src/OpenApiLibCore/keyword_logic/path_invalidation.py
similarity index 56%
rename from src/OpenApiLibCore/path_invalidation.py
rename to src/OpenApiLibCore/keyword_logic/path_invalidation.py
index 31cd042..97fb377 100644
--- a/src/OpenApiLibCore/path_invalidation.py
+++ b/src/OpenApiLibCore/keyword_logic/path_invalidation.py
@@ -1,24 +1,43 @@
"""Module holding functions related to invalidation of paths and urls."""
from random import choice
+from typing import Literal, overload
from uuid import uuid4
from robot.libraries.BuiltIn import BuiltIn
-from OpenApiLibCore.protocols import GetPathDtoClassType
+from OpenApiLibCore.models import oas_models
run_keyword = BuiltIn().run_keyword
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_parameterized_path_from_url"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["overload_default"], *args: object
+) -> object: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
def get_invalidated_url(
valid_url: str,
- path: str,
base_url: str,
- get_path_dto_class: GetPathDtoClassType,
+ openapi_spec: oas_models.OpenApiObject,
expected_status_code: int,
) -> str:
- dto_class = get_path_dto_class(path=path)
- relations = dto_class.get_path_relations()
+ path = _run_keyword("get_parameterized_path_from_url", valid_url)
+ path_item = openapi_spec.paths[path]
+
+ relations_mapping = path_item.relations_mapping
+ relations = relations_mapping.get_path_relations() if relations_mapping else []
paths = [
p.invalid_value
for p in relations
@@ -27,7 +46,7 @@ def get_invalidated_url(
if paths:
url = f"{base_url}{choice(paths)}"
return url
- parameterized_path: str = run_keyword("get_parameterized_path_from_url", valid_url)
+ parameterized_path = _run_keyword("get_parameterized_path_from_url", valid_url)
parameterized_url = base_url + parameterized_path
valid_url_parts = list(reversed(valid_url.split("/")))
parameterized_parts = reversed(parameterized_url.split("/"))
diff --git a/src/OpenApiLibCore/keyword_logic/resource_relations.py b/src/OpenApiLibCore/keyword_logic/resource_relations.py
new file mode 100644
index 0000000..0648cfe
--- /dev/null
+++ b/src/OpenApiLibCore/keyword_logic/resource_relations.py
@@ -0,0 +1,79 @@
+"""Module holding the functions related to relations between resources."""
+
+from typing import Literal, overload
+
+from requests import Response
+from robot.api import logger
+from robot.libraries.BuiltIn import BuiltIn
+
+import OpenApiLibCore.keyword_logic.path_functions as _path_functions
+from OpenApiLibCore.models.oas_models import OpenApiObject
+from OpenApiLibCore.models.request_data import RequestData
+from OpenApiLibCore.models.resource_relations import IdReference
+
+run_keyword = BuiltIn().run_keyword
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_request_data"], *args: str
+) -> RequestData: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_valid_url"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["authorized_request"], *args: object
+) -> Response: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
+def ensure_in_use(
+ url: str,
+ base_url: str,
+ openapi_spec: OpenApiObject,
+ resource_relation: IdReference,
+) -> None:
+ resource_id = ""
+
+ path = url.replace(base_url, "")
+ path_parts = path.split("/")
+ parameterized_path = _path_functions.get_parametrized_path(
+ path=path, openapi_spec=openapi_spec
+ )
+ parameterized_path_parts = parameterized_path.split("/")
+ for part, param_part in zip(
+ reversed(path_parts), reversed(parameterized_path_parts)
+ ):
+ if param_part.endswith("}"):
+ resource_id = part
+ break
+ if not resource_id:
+ raise ValueError(f"The provided url ({url}) does not contain an id.")
+ request_data = _run_keyword("get_request_data", resource_relation.post_path, "post")
+ json_data = request_data.valid_data if request_data.valid_data else {}
+ # FIXME: currently only works for object / dict data
+ if isinstance(json_data, dict):
+ json_data[resource_relation.property_name] = resource_id
+ post_url = _run_keyword("get_valid_url", resource_relation.post_path)
+ response = _run_keyword(
+ "authorized_request",
+ post_url,
+ "post",
+ request_data.params,
+ request_data.headers,
+ json_data,
+ )
+ if not response.ok:
+ logger.debug(
+ f"POST on {post_url} with json {json_data} failed: {response.json()}"
+ )
+ response.raise_for_status()
diff --git a/src/OpenApiLibCore/validation.py b/src/OpenApiLibCore/keyword_logic/validation.py
similarity index 83%
rename from src/OpenApiLibCore/validation.py
rename to src/OpenApiLibCore/keyword_logic/validation.py
index 4df33b5..b4e15eb 100644
--- a/src/OpenApiLibCore/validation.py
+++ b/src/OpenApiLibCore/keyword_logic/validation.py
@@ -3,7 +3,7 @@
import json as _json
from enum import Enum
from http import HTTPStatus
-from typing import Any, Mapping
+from typing import Any, Literal, Mapping, overload
from openapi_core.contrib.requests import (
RequestsOpenAPIRequest,
@@ -18,17 +18,52 @@
from robot.api.exceptions import Failure
from robot.libraries.BuiltIn import BuiltIn
-from OpenApiLibCore.models import (
+from OpenApiLibCore.annotations import JSON
+from OpenApiLibCore.models.oas_models import (
OpenApiObject,
ResponseObject,
UnionTypeSchema,
)
-from OpenApiLibCore.protocols import ResponseValidatorType
-from OpenApiLibCore.request_data import RequestData, RequestValues
+from OpenApiLibCore.models.request_data import RequestData, RequestValues
+from OpenApiLibCore.protocols import IResponseValidator
run_keyword = BuiltIn().run_keyword
+@overload
+def _run_keyword(
+ keyword_name: Literal["validate_response"], *args: object
+) -> None: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["authorized_request"], *args: object
+) -> Response: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_request_data"], *args: str
+) -> RequestData: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["validate_send_response"], *args: Response | JSON
+) -> None: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["assert_href_to_resource_is_valid"], *args: str | JSON
+) -> None: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
class ValidationLevel(str, Enum):
"""The available levels for the response_validation parameter."""
@@ -44,7 +79,7 @@ def perform_validated_request(
request_values: RequestValues,
original_data: Mapping[str, Any],
) -> None:
- response = run_keyword(
+ response = _run_keyword(
"authorized_request",
request_values.url,
request_values.method,
@@ -78,13 +113,13 @@ def perform_validated_request(
f"Response status_code {response.status_code} was not {status_code}."
)
- run_keyword("validate_response", path, response, original_data)
+ _run_keyword("validate_response", path, response, original_data)
if request_values.method == "DELETE":
- request_data: RequestData = run_keyword("get_request_data", path, "GET")
+ request_data = _run_keyword("get_request_data", path, "GET")
get_params = request_data.params
get_headers = request_data.headers
- get_response = run_keyword(
+ get_response = _run_keyword(
"authorized_request", request_values.url, "GET", get_params, get_headers
)
if response.ok:
@@ -109,13 +144,13 @@ def perform_validated_request(
def validate_response(
path: str,
response: Response,
- response_validator: ResponseValidatorType,
+ response_validator: IResponseValidator,
server_validation_warning_logged: bool,
disable_server_validation: bool,
- invalid_property_default_response: int,
+ invalid_data_default_response: int,
response_validation: str,
openapi_spec: OpenApiObject,
- original_data: Mapping[str, Any],
+ original_data: JSON,
) -> None:
if response.status_code == int(HTTPStatus.NO_CONTENT):
assert not response.content
@@ -127,7 +162,7 @@ def validate_response(
response_validator=response_validator,
server_validation_warning_logged=server_validation_warning_logged,
disable_server_validation=disable_server_validation,
- invalid_property_default_response=invalid_property_default_response,
+ invalid_data_default_response=invalid_data_default_response,
response_validation=response_validation,
)
except OpenAPIError as exception:
@@ -188,24 +223,24 @@ def validate_response(
# ensure the href is valid if the response is an object that contains a href
if isinstance(json_response, dict):
if href := json_response.get("href"):
- run_keyword("assert_href_to_resource_is_valid", href, json_response)
+ _run_keyword("assert_href_to_resource_is_valid", href, json_response)
# every property that was sucessfully send and that is in the response
# schema must have the value that was send
if response.ok and response.request.method in ["POST", "PUT", "PATCH"]:
- run_keyword("validate_send_response", response, original_data)
+ _run_keyword("validate_send_response", response, original_data)
return None
def assert_href_to_resource_is_valid(
- href: str, origin: str, base_url: str, referenced_resource: dict[str, Any]
+ href: str, origin: str, base_url: str, referenced_resource: JSON
) -> None:
url = f"{origin}{href}"
path = url.replace(base_url, "")
- request_data: RequestData = run_keyword("get_request_data", path, "GET")
+ request_data = _run_keyword("get_request_data", path, "GET")
params = request_data.params
headers = request_data.headers
- get_response = run_keyword("authorized_request", url, "GET", params, headers)
+ get_response = _run_keyword("authorized_request", url, "GET", params, headers)
assert get_response.json() == referenced_resource, (
f"{get_response.json()} not equal to original {referenced_resource}"
)
@@ -299,6 +334,9 @@ def validate_dict_response(
if original_data:
for send_property_name, send_value in original_data.items():
if send_property_name not in send_json.keys():
+ if send_property_name not in response_data:
+ logger.debug(f"'{send_property_name}' not found in response data.")
+ continue
assert send_value == response_data[send_property_name], (
f"Received value for {send_property_name} '{response_data[send_property_name]}' does not "
f"match '{send_value}' in the pre-patch data"
@@ -310,7 +348,7 @@ def validate_dict_response(
def validate_response_using_validator(
response: Response,
- response_validator: ResponseValidatorType,
+ response_validator: IResponseValidator,
) -> None:
openapi_request = RequestsOpenAPIRequest(response.request)
openapi_response = RequestsOpenAPIResponse(response)
@@ -319,13 +357,23 @@ def validate_response_using_validator(
def _validate_response(
response: Response,
- response_validator: ResponseValidatorType,
+ response_validator: IResponseValidator,
server_validation_warning_logged: bool,
disable_server_validation: bool,
- invalid_property_default_response: int,
+ invalid_data_default_response: int,
response_validation: str,
) -> None:
try:
+ content_type = response.headers.get("Content-Type", "")
+ if content_type:
+ key_value = "Content-Type"
+ else:
+ content_type = response.headers.get("content-type", "")
+ if content_type:
+ key_value = "content-type"
+ if "json" in content_type.lower():
+ content_type, _, _ = content_type.partition(";")
+ response.headers.update({key_value: content_type}) # pyright: ignore[reportPossiblyUnboundVariable]
validate_response_using_validator(
response=response,
response_validator=response_validator,
@@ -354,7 +402,7 @@ def _validate_response(
if disable_server_validation:
return
- if response.status_code == invalid_property_default_response:
+ if response.status_code == invalid_data_default_response:
logger.debug(error_message)
return
if response_validation == ValidationLevel.STRICT:
@@ -372,7 +420,7 @@ def _get_response_object(
method = method.lower()
status = str(status_code)
path_item = openapi_spec.paths[path]
- path_operations = path_item.get_operations()
+ path_operations = path_item.operations
operation_data = path_operations.get(method)
if operation_data is None:
raise ValueError(f"method '{method}' not supported for {path}.")
diff --git a/src/OpenApiLibCore/models.py b/src/OpenApiLibCore/models.py
deleted file mode 100644
index e37d9c0..0000000
--- a/src/OpenApiLibCore/models.py
+++ /dev/null
@@ -1,759 +0,0 @@
-import base64
-from abc import abstractmethod
-from collections import ChainMap
-from functools import cached_property
-from random import choice, randint, uniform
-from sys import float_info
-from typing import (
- Generator,
- Generic,
- Literal,
- Mapping,
- TypeAlias,
- TypeVar,
-)
-
-import rstr
-from pydantic import BaseModel, Field, RootModel
-from robot.api import logger
-
-from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.localized_faker import FAKE, fake_string
-
-EPSILON = float_info.epsilon
-
-O = TypeVar("O")
-
-
-class SchemaBase(BaseModel, Generic[O], frozen=True):
- readOnly: bool = False
- writeOnly: bool = False
-
- @abstractmethod
- def get_valid_value(self) -> JSON: ...
-
- @abstractmethod
- def get_values_out_of_bounds(self, current_value: O) -> list[O]: ...
-
- @abstractmethod
- def get_invalid_value_from_const_or_enum(self) -> O: ...
-
-
-class NullSchema(SchemaBase[None], frozen=True):
- type: Literal["null"] = "null"
-
- def get_valid_value(self) -> None:
- return None
-
- def get_values_out_of_bounds(self, current_value: None) -> list[None]:
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> None:
- raise ValueError
-
- @property
- def can_be_invalidated(self) -> bool:
- return False
-
- @property
- def annotation_string(self) -> str:
- return "None"
-
-
-class BooleanSchema(SchemaBase[bool], frozen=True):
- type: Literal["boolean"] = "boolean"
- const: bool | None = None
- nullable: bool = False
-
- def get_valid_value(self) -> bool:
- if self.const is not None:
- return self.const
- return choice([True, False])
-
- def get_values_out_of_bounds(self, current_value: bool) -> list[bool]:
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> bool:
- if self.const is not None:
- return not self.const
- raise ValueError
-
- @property
- def can_be_invalidated(self) -> bool:
- return True
-
- @property
- def annotation_string(self) -> str:
- return "bool"
-
-
-class StringSchema(SchemaBase[str], frozen=True):
- type: Literal["string"] = "string"
- format: str = ""
- pattern: str = ""
- maxLength: int | None = None
- minLength: int | None = None
- const: str | None = None
- enum: list[str] | None = None
- nullable: bool = False
-
- def get_valid_value(self) -> bytes | str:
- """Generate a random string within the min/max length in the schema, if specified."""
- if self.const is not None:
- return self.const
- if self.enum is not None:
- return choice(self.enum)
- # if a pattern is provided, format and min/max length can be ignored
- if pattern := self.pattern:
- try:
- return rstr.xeger(pattern)
- except Exception as exception:
- logger.warn(
- f"An error occured trying to generate a string matching the "
- f"pattern defined in the specification. To ensure a valid value "
- f"is generated for this property, a PropertyValueConstraint can be "
- f"configured. See the Advanced Use section of the OpenApiTools "
- f"documentation for more details."
- f"\nThe exception was: {exception}\nThe pattern was: {pattern}"
- )
- minimum = self.minLength if self.minLength is not None else 0
- maximum = self.maxLength if self.maxLength is not None else 36
- maximum = max(minimum, maximum)
- format_ = self.format if self.format else "uuid"
- # byte is a special case due to the required encoding
- if format_ == "byte":
- data = FAKE.uuid()
- return base64.b64encode(data.encode("utf-8"))
- value = fake_string(string_format=format_)
- while len(value) < minimum:
- # use fake.name() to ensure the returned string uses the provided locale
- value = value + FAKE.name()
- if len(value) > maximum:
- value = value[:maximum]
- return value
-
- def get_values_out_of_bounds(self, current_value: str) -> list[str]:
- invalid_values: list[str] = []
- if self.minLength:
- invalid_values.append(current_value[0 : self.minLength - 1])
- # if there is a maximum length, send 1 character more
- if self.maxLength:
- invalid_string_value = current_value if current_value else "x"
- # add random characters from the current value to prevent adding new characters
- while len(invalid_string_value) <= self.maxLength:
- invalid_string_value += choice(invalid_string_value)
- invalid_values.append(invalid_string_value)
- if invalid_values:
- return invalid_values
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> str:
- valid_values = []
- if self.const is not None:
- valid_values = [self.const]
- if self.enum is not None:
- valid_values = self.enum
-
- if not valid_values:
- raise ValueError
-
- invalid_value = ""
- for value in valid_values:
- invalid_value += value + value
-
- return invalid_value
-
- @property
- def can_be_invalidated(self) -> bool:
- if (
- self.maxLength is not None
- or self.minLength is not None
- or self.const is not None
- or self.enum is not None
- ):
- return True
- return False
-
- @property
- def annotation_string(self) -> str:
- return "str"
-
-
-class IntegerSchema(SchemaBase[int], frozen=True):
- type: Literal["integer"] = "integer"
- format: str = "int32"
- maximum: int | None = None
- exclusiveMaximum: int | bool | None = None
- minimum: int | None = None
- exclusiveMinimum: int | bool | None = None
- multipleOf: int | None = None # TODO: implement support
- const: int | None = None
- enum: list[int] | None = None
- nullable: bool = False
-
- @cached_property
- def _max_int(self) -> int:
- if self.format == "int64":
- return 9223372036854775807
- return 2147483647
-
- @cached_property
- def _min_int(self) -> int:
- if self.format == "int64":
- return -9223372036854775808
- return -2147483648
-
- @cached_property
- def _max_value(self) -> int:
- # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
- # OAS 3.1: exclusiveMinimum/Maximum is an integer
- if isinstance(self.exclusiveMaximum, int) and not isinstance(
- self.exclusiveMaximum, bool
- ):
- return self.exclusiveMaximum - 1
-
- if isinstance(self.maximum, int):
- if self.exclusiveMaximum is True:
- return self.maximum - 1
- return self.maximum
-
- return self._max_int
-
- @cached_property
- def _min_value(self) -> int:
- # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
- # OAS 3.1: exclusiveMinimum/Maximum is an integer
- if isinstance(self.exclusiveMinimum, int) and not isinstance(
- self.exclusiveMinimum, bool
- ):
- return self.exclusiveMinimum + 1
-
- if isinstance(self.minimum, int):
- if self.exclusiveMinimum is True:
- return self.minimum + 1
- return self.minimum
-
- return self._min_int
-
- def get_valid_value(self) -> int:
- """Generate a random int within the min/max range of the schema, if specified."""
- if self.const is not None:
- return self.const
- if self.enum is not None:
- return choice(self.enum)
-
- return randint(self._min_value, self._max_value)
-
- def get_values_out_of_bounds(self, current_value: int) -> list[int]: # pylint: disable=unused-argument
- invalid_values: list[int] = []
-
- if self._min_value > self._min_int:
- invalid_values.append(self._min_value - 1)
-
- if self._max_value < self._max_int:
- invalid_values.append(self._max_value + 1)
-
- if invalid_values:
- return invalid_values
-
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> int:
- valid_values = []
- if self.const is not None:
- valid_values = [self.const]
- if self.enum is not None:
- valid_values = self.enum
-
- if not valid_values:
- raise ValueError
-
- invalid_value = 0
- for value in valid_values:
- invalid_value += abs(value) + abs(value)
-
- return invalid_value
-
- @property
- def can_be_invalidated(self) -> bool:
- return True
-
- @property
- def annotation_string(self) -> str:
- return "int"
-
-
-class NumberSchema(SchemaBase[float], frozen=True):
- type: Literal["number"] = "number"
- maximum: int | float | None = None
- exclusiveMaximum: int | float | bool | None = None
- minimum: int | float | None = None
- exclusiveMinimum: int | float | bool | None = None
- multipleOf: int | None = None # TODO: implement support
- const: int | float | None = None
- enum: list[int | float] | None = None
- nullable: bool = False
-
- @cached_property
- def _max_float(self) -> float:
- return 9223372036854775807.0
-
- @cached_property
- def _min_float(self) -> float:
- return -9223372036854775808.0
-
- @cached_property
- def _max_value(self) -> float:
- # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
- # OAS 3.1: exclusiveMinimum/Maximum is an integer or a float
- if isinstance(self.exclusiveMaximum, (int, float)) and not isinstance(
- self.exclusiveMaximum, bool
- ):
- return self.exclusiveMaximum - 0.0000000001
-
- if isinstance(self.maximum, (int, float)):
- if self.exclusiveMaximum is True:
- return self.maximum - 0.0000000001
- return self.maximum
-
- return self._max_float
-
- @cached_property
- def _min_value(self) -> float:
- # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
- # OAS 3.1: exclusiveMinimum/Maximum is an integer or a float
- if isinstance(self.exclusiveMinimum, (int, float)) and not isinstance(
- self.exclusiveMinimum, bool
- ):
- return self.exclusiveMinimum + 0.0000000001
-
- if isinstance(self.minimum, (int, float)):
- if self.exclusiveMinimum is True:
- return self.minimum + 0.0000000001
- return self.minimum
-
- return self._min_float
-
- def get_valid_value(self) -> float:
- """Generate a random float within the min/max range of the schema, if specified."""
- if self.const is not None:
- return self.const
- if self.enum is not None:
- return choice(self.enum)
-
- return uniform(self._min_value, self._max_value)
-
- def get_values_out_of_bounds(self, current_value: float) -> list[float]: # pylint: disable=unused-argument
- invalid_values: list[float] = []
-
- if self._min_value > self._min_float:
- invalid_values.append(self._min_value - 0.000000001)
-
- if self._max_value < self._max_float:
- invalid_values.append(self._max_value + 0.000000001)
-
- if invalid_values:
- return invalid_values
-
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> float:
- valid_values = []
- if self.const is not None:
- valid_values = [self.const]
- if self.enum is not None:
- valid_values = self.enum
-
- if not valid_values:
- raise ValueError
-
- invalid_value = 0.0
- for value in valid_values:
- invalid_value += abs(value) + abs(value)
-
- return invalid_value
-
- @property
- def can_be_invalidated(self) -> bool:
- return True
-
- @property
- def annotation_string(self) -> str:
- return "float"
-
-
-class ArraySchema(SchemaBase[list[JSON]], frozen=True):
- type: Literal["array"] = "array"
- items: "SchemaObjectTypes"
- maxItems: int | None = None
- minItems: int | None = None
- uniqueItems: bool = False
- const: list[JSON] | None = None
- enum: list[list[JSON]] | None = None
- nullable: bool = False
-
- def get_valid_value(self) -> list[JSON]:
- if self.const is not None:
- return self.const
-
- if self.enum is not None:
- return choice(self.enum)
-
- minimum = self.minItems if self.minItems is not None else 0
- maximum = self.maxItems if self.maxItems is not None else 1
- maximum = max(minimum, maximum)
-
- value: list[JSON] = []
- for _ in range(maximum):
- item_value = self.items.get_valid_value()
- value.append(item_value)
- return value
-
- def get_values_out_of_bounds(self, current_value: list[JSON]) -> list[list[JSON]]:
- invalid_values: list[list[JSON]] = []
-
- if self.minItems:
- invalid_value = current_value[0 : self.minItems - 1]
- invalid_values.append(invalid_value)
-
- if self.maxItems is not None:
- invalid_value = []
- if not current_value:
- current_value = self.get_valid_value()
-
- if not current_value:
- current_value = [self.items.get_valid_value()]
-
- while len(invalid_value) <= self.maxItems:
- invalid_value.append(choice(current_value))
- invalid_values.append(invalid_value)
-
- if invalid_values:
- return invalid_values
-
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> list[JSON]:
- valid_values = []
- if self.const is not None:
- valid_values = [self.const]
- if self.enum is not None:
- valid_values = self.enum
-
- if not valid_values:
- raise ValueError
-
- invalid_value = []
- for value in valid_values:
- invalid_value.extend(value)
- invalid_value.extend(value)
-
- return invalid_value
-
- @property
- def can_be_invalidated(self) -> bool:
- if (
- self.maxItems is not None
- or self.minItems is not None
- or self.uniqueItems
- or self.const is not None
- or self.enum is not None
- ):
- return True
- if isinstance(self.items, (BooleanSchema, IntegerSchema, NumberSchema)):
- return True
- return False
-
- @property
- def annotation_string(self) -> str:
- return f"list[{self.items.annotation_string}]"
-
-
-# NOTE: Workaround for cyclic PropertiesMapping / SchemaObjectTypes annotations
-def _get_properties_mapping_default() -> "PropertiesMapping":
- return _get_empty_properties_mapping()
-
-
-class ObjectSchema(SchemaBase[dict[str, JSON]], frozen=True):
- type: Literal["object"] = "object"
- properties: "PropertiesMapping" = Field(
- default_factory=_get_properties_mapping_default
- )
- additionalProperties: "bool | SchemaObjectTypes" = True
- required: list[str] = []
- maxProperties: int | None = None
- minProperties: int | None = None
- const: dict[str, JSON] | None = None
- enum: list[dict[str, JSON]] | None = None
- nullable: bool = False
-
- def get_valid_value(self) -> dict[str, JSON]:
- raise NotImplementedError
-
- def get_values_out_of_bounds(
- self, current_value: Mapping[str, JSON]
- ) -> list[dict[str, JSON]]:
- raise ValueError
-
- def get_invalid_value_from_const_or_enum(self) -> dict[str, JSON]:
- valid_values = []
- if self.const is not None:
- valid_values = [self.const]
- if self.enum is not None:
- valid_values = self.enum
-
- if not valid_values:
- raise ValueError
-
- # This invalidation will not work for a const and may not work for
- # an enum. In that case a different invalidation approach will be used.
- invalid_value = {**valid_values[0]}
- for value in valid_values:
- for key in invalid_value.keys():
- invalid_value[key] = value.get(key)
- if invalid_value not in valid_values:
- return invalid_value
-
- raise ValueError
-
- @property
- def can_be_invalidated(self) -> bool:
- if (
- self.required
- or self.maxProperties is not None
- or self.minProperties is not None
- or self.const is not None
- or self.enum is not None
- ):
- return True
- return False
-
- @property
- def annotation_string(self) -> str:
- return "dict[str, Any]"
-
-
-ResolvedSchemaObjectTypes: TypeAlias = (
- NullSchema
- | BooleanSchema
- | StringSchema
- | IntegerSchema
- | NumberSchema
- | ArraySchema
- | ObjectSchema
-)
-
-
-class UnionTypeSchema(SchemaBase[JSON], frozen=True):
- allOf: list["SchemaObjectTypes"] = []
- anyOf: list["SchemaObjectTypes"] = []
- oneOf: list["SchemaObjectTypes"] = []
-
- def get_valid_value(self) -> JSON:
- chosen_schema = choice(self.resolved_schemas)
- return chosen_schema.get_valid_value()
-
- def get_values_out_of_bounds(self, current_value: JSON) -> list[JSON]:
- raise ValueError
-
- @property
- def resolved_schemas(self) -> list[ResolvedSchemaObjectTypes]:
- return list(self._get_resolved_schemas())
-
- def _get_resolved_schemas(self) -> Generator[ResolvedSchemaObjectTypes, None, None]:
- if self.allOf:
- properties_list: list[PropertiesMapping] = []
- additional_properties_list = []
- required_list = []
- max_properties_list = []
- min_properties_list = []
- nullable_list = []
-
- for schema in self.allOf:
- if not isinstance(schema, ObjectSchema):
- raise NotImplementedError("allOf only supported for ObjectSchemas")
-
- if schema.const is not None:
- raise ValueError("allOf and models with a const are not compatible")
-
- if schema.enum:
- raise ValueError("allOf and models with enums are not compatible")
-
- if schema.properties:
- properties_list.append(schema.properties)
- additional_properties_list.append(schema.additionalProperties)
- required_list += schema.required
- max_properties_list.append(schema.maxProperties)
- min_properties_list.append(schema.minProperties)
- nullable_list.append(schema.nullable)
-
- properties_dicts = [mapping.root for mapping in properties_list]
- properties = dict(ChainMap(*properties_dicts))
-
- if True in additional_properties_list:
- additional_properties_value: bool | SchemaObjectTypes = True
- else:
- additional_properties_types = []
- for additional_properties_item in additional_properties_list:
- if isinstance(
- additional_properties_item, ResolvedSchemaObjectTypes
- ):
- additional_properties_types.append(additional_properties_item)
- if not additional_properties_types:
- additional_properties_value = False
- else:
- additional_properties_value = UnionTypeSchema(
- anyOf=additional_properties_types,
- )
-
- max_properties = [max for max in max_properties_list if max is not None]
- min_properties = [min for min in min_properties_list if min is not None]
- max_propeties_value = max(max_properties) if max_properties else None
- min_propeties_value = min(min_properties) if min_properties else None
-
- merged_schema = ObjectSchema(
- type="object",
- properties=properties,
- additionalProperties=additional_properties_value,
- required=required_list,
- maxProperties=max_propeties_value,
- minProperties=min_propeties_value,
- nullable=all(nullable_list),
- )
- yield merged_schema
- else:
- for schema in self.anyOf + self.oneOf:
- if isinstance(schema, ResolvedSchemaObjectTypes):
- yield schema
- else:
- yield from schema.resolved_schemas
-
- def get_invalid_value_from_const_or_enum(self) -> JSON:
- raise ValueError
-
- @property
- def annotation_string(self) -> str:
- unique_annotations = {s.annotation_string for s in self.resolved_schemas}
- return " | ".join(unique_annotations)
-
-
-SchemaObjectTypes: TypeAlias = ResolvedSchemaObjectTypes | UnionTypeSchema
-
-
-class PropertiesMapping(RootModel[dict[str, "SchemaObjectTypes"]], frozen=True): ...
-
-
-def _get_empty_properties_mapping() -> PropertiesMapping:
- return PropertiesMapping(root={})
-
-
-class ParameterObject(BaseModel):
- name: str
- in_: str = Field(..., alias="in")
- required: bool = False
- description: str = ""
- schema_: SchemaObjectTypes | None = Field(None, alias="schema")
-
-
-class MediaTypeObject(BaseModel):
- schema_: SchemaObjectTypes | None = Field(None, alias="schema")
-
-
-class RequestBodyObject(BaseModel):
- content: dict[str, MediaTypeObject]
- required: bool = False
- description: str = ""
-
- @cached_property
- def schema_(self) -> SchemaObjectTypes | None:
- if not self.mime_type:
- return None
-
- if len(self._json_schemas) > 1:
- logger.info(
- f"Multiple JSON media types defined for requestBody, "
- f"using the first candidate from {self.content}"
- )
- return self._json_schemas[self.mime_type]
-
- @cached_property
- def mime_type(self) -> str | None:
- if not self._json_schemas:
- return None
-
- return next(iter(self._json_schemas))
-
- @cached_property
- def _json_schemas(self) -> dict[str, SchemaObjectTypes]:
- json_schemas = {
- mime_type: media_type.schema_
- for mime_type, media_type in self.content.items()
- if "json" in mime_type and media_type.schema_ is not None
- }
- return json_schemas
-
-
-class HeaderObject(BaseModel): ...
-
-
-class LinkObject(BaseModel): ...
-
-
-class ResponseObject(BaseModel):
- description: str
- content: dict[str, MediaTypeObject] = {}
- headers: dict[str, HeaderObject] = {}
- links: dict[str, LinkObject] = {}
-
-
-class OperationObject(BaseModel):
- operationId: str | None = None
- summary: str = ""
- description: str = ""
- tags: list[str] = []
- parameters: list[ParameterObject] = []
- requestBody: RequestBodyObject | None = None
- responses: dict[str, ResponseObject] = {}
-
- def update_parameters(self, parameters: list[ParameterObject]) -> None:
- self.parameters.extend(parameters)
-
-
-class PathItemObject(BaseModel):
- get: OperationObject | None = None
- post: OperationObject | None = None
- patch: OperationObject | None = None
- put: OperationObject | None = None
- delete: OperationObject | None = None
- summary: str = ""
- description: str = ""
- parameters: list[ParameterObject] = []
-
- def get_operations(self) -> dict[str, OperationObject]:
- return {
- k: v for k, v in self.__dict__.items() if isinstance(v, OperationObject)
- }
-
- def update_operation_parameters(self) -> None:
- if not self.parameters:
- return
-
- operations_to_update = self.get_operations()
- for operation_object in operations_to_update.values():
- operation_object.update_parameters(self.parameters)
-
-
-class InfoObject(BaseModel):
- title: str
- version: str
- summary: str = ""
- description: str = ""
-
-
-class OpenApiObject(BaseModel):
- info: InfoObject
- paths: dict[str, PathItemObject]
-
- def model_post_init(self, context: object) -> None:
- for path_object in self.paths.values():
- path_object.update_operation_parameters()
diff --git a/src/OpenApiLibCore/models/__init__.py b/src/OpenApiLibCore/models/__init__.py
new file mode 100644
index 0000000..dd35889
--- /dev/null
+++ b/src/OpenApiLibCore/models/__init__.py
@@ -0,0 +1,17 @@
+class Ignore:
+ """Helper class to flag properties to be ignored in data generation."""
+
+ def __str__(self) -> str:
+ return "IGNORE" # pragma: no cover
+
+
+class UnSet:
+ """Helper class to flag arguments that have not been set in a keyword call."""
+
+ def __str__(self) -> str:
+ return "UNSET" # pragma: no cover
+
+
+IGNORE = Ignore()
+
+UNSET = UnSet()
diff --git a/src/OpenApiLibCore/models/oas_models.py b/src/OpenApiLibCore/models/oas_models.py
new file mode 100644
index 0000000..cddc927
--- /dev/null
+++ b/src/OpenApiLibCore/models/oas_models.py
@@ -0,0 +1,1438 @@
+from __future__ import annotations
+
+import builtins
+from abc import abstractmethod
+from collections import ChainMap
+from copy import deepcopy
+from functools import cached_property
+from random import choice, randint, sample, shuffle, uniform
+from sys import float_info
+from typing import (
+ Annotated,
+ Any,
+ Callable,
+ Generator,
+ Generic,
+ Iterable,
+ Literal,
+ Mapping,
+ TypeAlias,
+ TypeGuard,
+ TypeVar,
+ Union,
+ cast,
+)
+from uuid import uuid4
+
+import rstr
+from pydantic import BaseModel, Field, RootModel
+from robot.api import logger
+from robot.libraries.BuiltIn import BuiltIn
+
+from OpenApiLibCore.annotations import JSON
+from OpenApiLibCore.data_generation.localized_faker import FAKE, fake_string
+from OpenApiLibCore.data_generation.value_utils import (
+ json_type_name_of_python_type,
+ python_type_by_json_type_name,
+)
+from OpenApiLibCore.data_relations.relations_base import RelationsMapping
+from OpenApiLibCore.models import IGNORE, Ignore
+from OpenApiLibCore.models.resource_relations import (
+ NOT_SET,
+ IdDependency,
+ PropertyValueConstraint,
+)
+from OpenApiLibCore.protocols import RelationsMappingType
+from OpenApiLibCore.utils.id_mapping import dummy_transformer
+from OpenApiLibCore.utils.parameter_utils import get_safe_name_for_oas_name
+
+run_keyword = BuiltIn().run_keyword
+
+EPSILON = float_info.epsilon
+
+SENTINEL = object()
+
+O = TypeVar("O")
+AI = TypeVar("AI", bound=JSON)
+
+
+def is_object_schema(schema: SchemaObjectTypes) -> TypeGuard[ObjectSchema]:
+ return isinstance(schema, ObjectSchema)
+
+
+class SchemaBase(BaseModel, Generic[O], frozen=True):
+ readOnly: bool = False
+ writeOnly: bool = False
+ relations_mapping: RelationsMappingType = RelationsMapping # type: ignore[assignment]
+
+ @abstractmethod
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[O, SchemaObjectTypes]: ...
+
+ @abstractmethod
+ def get_values_out_of_bounds(self, current_value: O) -> list[O]: ...
+
+ @abstractmethod
+ def get_invalid_value_from_const_or_enum(self) -> O: ...
+
+ @abstractmethod
+ def get_invalid_value_from_constraint(self, values_from_constraint: list[O]) -> O:
+ """
+ Return a value of the same type as the values in the values_from_constraints that
+ is not in the values_from_constraints, if possible. Otherwise raise ValueError.
+ """
+
+ def get_invalid_value(
+ self,
+ valid_value: O,
+ values_from_constraint: Iterable[O] = tuple(),
+ ) -> O | str | list[JSON] | Ignore:
+ """Return a random value that violates the provided value_schema."""
+ invalid_values: list[O | str | list[JSON] | Ignore] = []
+ value_type = getattr(self, "type")
+
+ if not isinstance(valid_value, python_type_by_json_type_name(value_type)):
+ valid_value = self.get_valid_value()[0]
+
+ if values_from_constraint:
+ # if IGNORE is in the values_from_constraints, the parameter needs to be
+ # ignored for an OK response so leaving the value at it's original value
+ # should result in the specified error response
+ if any(map(lambda x: isinstance(x, Ignore), values_from_constraint)):
+ return IGNORE
+ try:
+ return self.get_invalid_value_from_constraint(
+ values_from_constraint=list(values_from_constraint),
+ )
+ except ValueError:
+ pass
+
+ # For schemas with a const or enum, add invalidated values from those
+ try:
+ invalid_value = self.get_invalid_value_from_const_or_enum()
+ invalid_values.append(invalid_value)
+ except ValueError:
+ pass
+
+ # Violate min / max values or length if possible
+ try:
+ values_out_of_bounds = self.get_values_out_of_bounds(
+ current_value=valid_value
+ )
+ invalid_values += values_out_of_bounds
+ except ValueError:
+ pass
+
+ # No value constraints or min / max ranges to violate, so change the data type
+ if value_type == "string":
+ # Since int / float / bool can always be cast to sting, change
+ # the string to a nested object.
+ # An array gets exploded in query strings, "null" is then often invalid
+ invalid_values.append([{"invalid": [None, False]}, "null", None, True])
+ else:
+ invalid_values.append(FAKE.uuid())
+
+ return choice(invalid_values)
+
+ def attach_relations_mapping(self, relations_mapping: RelationsMappingType) -> None:
+ # NOTE: https://github.com/pydantic/pydantic/issues/11495
+ self.__dict__["relations_mapping"] = relations_mapping
+
+
+class NullSchema(SchemaBase[None], frozen=True):
+ type: Literal["null"] = "null"
+ nullable: bool = False
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[None, NullSchema]:
+ return None, self
+
+ def get_values_out_of_bounds(self, current_value: None) -> list[None]:
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> None:
+ raise ValueError
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[None]
+ ) -> None:
+ raise ValueError
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ return False
+
+ @property
+ def annotation_string(self) -> str:
+ return "None"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return type(None)
+
+
+class BooleanSchema(SchemaBase[bool], frozen=True):
+ type: Literal["boolean"] = "boolean"
+ const: bool | None = None
+ nullable: bool = False
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[bool, BooleanSchema]:
+ if self.const is not None:
+ return self.const, self
+ return choice([True, False]), self
+
+ def get_values_out_of_bounds(self, current_value: bool) -> list[bool]:
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> bool:
+ if self.const is not None:
+ return not self.const
+ raise ValueError
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[bool]
+ ) -> bool:
+ if len(values_from_constraint) == 1:
+ return not values_from_constraint[0]
+ raise ValueError
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ return True
+
+ @property
+ def annotation_string(self) -> str:
+ return "bool"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return bool
+
+
+class StringSchema(SchemaBase[str], frozen=True):
+ type: Literal["string"] = "string"
+ format: str = ""
+ pattern: str = ""
+ maxLength: int | None = None
+ minLength: int | None = None
+ const: str | None = None
+ enum: list[str] | None = None
+ nullable: bool = False
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[str, StringSchema]:
+ """Generate a random string within the min/max length in the schema, if specified."""
+ if self.const is not None:
+ return self.const, self
+ if self.enum is not None:
+ return choice(self.enum), self
+ # if a pattern is provided, format and min/max length can be ignored
+ if pattern := self.pattern:
+ try:
+ return rstr.xeger(pattern), self
+ except Exception as exception:
+ logger.warn(
+ f"An error occured trying to generate a string matching the "
+ f"pattern defined in the specification. To ensure a valid value "
+ f"is generated for this property, a PropertyValueConstraint can be "
+ f"configured. See the Advanced Use section of the OpenApiTools "
+ f"documentation for more details."
+ f"\nThe exception was: {exception}\nThe pattern was: {pattern}"
+ )
+ minimum = self.minLength if self.minLength is not None else 0
+ maximum = self.maxLength if self.maxLength is not None else 36
+ maximum = max(minimum, maximum)
+
+ format_ = self.format if self.format else "uuid"
+ value = fake_string(string_format=format_)
+ while len(value) < minimum:
+ value = value + fake_string(string_format=format_)
+ if len(value) > maximum:
+ value = value[:maximum]
+ return value, self
+
+ def get_values_out_of_bounds(self, current_value: str) -> list[str]:
+ invalid_values: list[str] = []
+ if self.minLength:
+ invalid_values.append(current_value[0 : self.minLength - 1])
+ # if there is a maximum length, send 1 character more
+ if self.maxLength:
+ invalid_value = current_value if current_value else "x"
+ # add random characters from the current value to prevent adding new characters
+ while len(invalid_value) <= self.maxLength:
+ invalid_value += choice(invalid_value)
+ invalid_values.append(invalid_value)
+ if invalid_values:
+ return invalid_values
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> str:
+ valid_values = []
+ if self.const is not None:
+ valid_values = [self.const]
+ if self.enum is not None:
+ valid_values = self.enum
+
+ if not valid_values:
+ raise ValueError
+
+ invalid_value = ""
+ for value in valid_values:
+ invalid_value += value + value
+
+ return invalid_value
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[str]
+ ) -> str:
+ invalid_values = 2 * values_from_constraint
+ invalid_value = invalid_values.pop()
+ for value in invalid_values:
+ invalid_value = invalid_value + value
+
+ if not invalid_value:
+ raise ValueError("Value invalidation yielded an empty string.")
+ return invalid_value
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ if (
+ self.maxLength is not None
+ or self.minLength is not None
+ or self.const is not None
+ or self.enum is not None
+ ):
+ return True
+ return False
+
+ @property
+ def annotation_string(self) -> str:
+ return "str"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return str
+
+
+class IntegerSchema(SchemaBase[int], frozen=True):
+ type: Literal["integer"] = "integer"
+ format: str = "int32"
+ maximum: int | None = None
+ exclusiveMaximum: int | bool | None = None
+ minimum: int | None = None
+ exclusiveMinimum: int | bool | None = None
+ multipleOf: int | None = None # TODO: implement support
+ const: int | None = None
+ enum: list[int] | None = None
+ nullable: bool = False
+
+ @cached_property
+ def _max_int(self) -> int:
+ if self.format == "int64":
+ return 9223372036854775807
+ return 2147483647
+
+ @cached_property
+ def _min_int(self) -> int:
+ if self.format == "int64":
+ return -9223372036854775808
+ return -2147483648
+
+ @cached_property
+ def _max_value(self) -> int:
+ # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
+ # OAS 3.1: exclusiveMinimum/Maximum is an integer
+ if isinstance(self.exclusiveMaximum, int) and not isinstance(
+ self.exclusiveMaximum, bool
+ ):
+ return self.exclusiveMaximum - 1
+
+ if isinstance(self.maximum, int):
+ if self.exclusiveMaximum is True:
+ return self.maximum - 1
+ return self.maximum
+
+ return self._max_int
+
+ @cached_property
+ def _min_value(self) -> int:
+ # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
+ # OAS 3.1: exclusiveMinimum/Maximum is an integer
+ if isinstance(self.exclusiveMinimum, int) and not isinstance(
+ self.exclusiveMinimum, bool
+ ):
+ return self.exclusiveMinimum + 1
+
+ if isinstance(self.minimum, int):
+ if self.exclusiveMinimum is True:
+ return self.minimum + 1
+ return self.minimum
+
+ return self._min_int
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[int, IntegerSchema]:
+ """Generate a random int within the min/max range of the schema, if specified."""
+ if self.const is not None:
+ return self.const, self
+ if self.enum is not None:
+ return choice(self.enum), self
+
+ return randint(self._min_value, self._max_value), self
+
+ def get_values_out_of_bounds(self, current_value: int) -> list[int]: # pylint: disable=unused-argument
+ invalid_values: list[int] = []
+
+ if self._min_value > self._min_int:
+ invalid_values.append(self._min_value - 1)
+
+ if self._max_value < self._max_int:
+ invalid_values.append(self._max_value + 1)
+
+ if invalid_values:
+ return invalid_values
+
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> int:
+ valid_values = []
+ if self.const is not None:
+ valid_values = [self.const]
+ if self.enum is not None:
+ valid_values = self.enum
+
+ if not valid_values:
+ raise ValueError
+
+ invalid_value = 0
+ for value in valid_values:
+ invalid_value += abs(value) + abs(value)
+
+ return invalid_value
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[int]
+ ) -> int:
+ invalid_values = 2 * values_from_constraint
+ invalid_value = invalid_values.pop()
+ for value in invalid_values:
+ invalid_value = abs(invalid_value) + abs(value)
+ if not invalid_value:
+ invalid_value += 1
+ return invalid_value
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ return True
+
+ @property
+ def annotation_string(self) -> str:
+ return "int"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return int
+
+
+class NumberSchema(SchemaBase[float], frozen=True):
+ type: Literal["number"] = "number"
+ maximum: int | float | None = None
+ exclusiveMaximum: int | float | bool | None = None
+ minimum: int | float | None = None
+ exclusiveMinimum: int | float | bool | None = None
+ multipleOf: int | None = None # TODO: implement support
+ const: int | float | None = None
+ enum: list[int | float] | None = None
+ nullable: bool = False
+
+ @cached_property
+ def _max_float(self) -> float:
+ return 9223372036854775807.0
+
+ @cached_property
+ def _min_float(self) -> float:
+ return -9223372036854775808.0
+
+ @cached_property
+ def _max_value(self) -> float:
+ # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
+ # OAS 3.1: exclusiveMinimum/Maximum is an integer or a float
+ if isinstance(self.exclusiveMaximum, (int, float)) and not isinstance(
+ self.exclusiveMaximum, bool
+ ):
+ return self.exclusiveMaximum - 0.0000000001
+
+ if isinstance(self.maximum, (int, float)):
+ if self.exclusiveMaximum is True:
+ return self.maximum - 0.0000000001
+ return self.maximum
+
+ return self._max_float
+
+ @cached_property
+ def _min_value(self) -> float:
+ # OAS 3.0: exclusiveMinimum/Maximum is a bool in combination with minimum/maximum
+ # OAS 3.1: exclusiveMinimum/Maximum is an integer or a float
+ if isinstance(self.exclusiveMinimum, (int, float)) and not isinstance(
+ self.exclusiveMinimum, bool
+ ):
+ return self.exclusiveMinimum + 0.0000000001
+
+ if isinstance(self.minimum, (int, float)):
+ if self.exclusiveMinimum is True:
+ return self.minimum + 0.0000000001
+ return self.minimum
+
+ return self._min_float
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[float, NumberSchema]:
+ """Generate a random float within the min/max range of the schema, if specified."""
+ if self.const is not None:
+ return self.const, self
+ if self.enum is not None:
+ return choice(self.enum), self
+
+ return uniform(self._min_value, self._max_value), self
+
+ def get_values_out_of_bounds(self, current_value: float) -> list[float]: # pylint: disable=unused-argument
+ invalid_values: list[float] = []
+
+ if self._min_value > self._min_float:
+ invalid_values.append(self._min_value - 0.000000001)
+
+ if self._max_value < self._max_float:
+ invalid_values.append(self._max_value + 0.000000001)
+
+ if invalid_values:
+ return invalid_values
+
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> float:
+ valid_values = []
+ if self.const is not None:
+ valid_values = [self.const]
+ if self.enum is not None:
+ valid_values = self.enum
+
+ if not valid_values:
+ raise ValueError
+
+ invalid_value = 0.0
+ for value in valid_values:
+ invalid_value += abs(value) + abs(value)
+
+ return invalid_value
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[float]
+ ) -> float:
+ invalid_values = 2 * values_from_constraint
+ invalid_value = invalid_values.pop()
+ for value in invalid_values:
+ invalid_value = abs(invalid_value) + abs(value)
+ if not invalid_value:
+ invalid_value += 1
+ return invalid_value
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ return True
+
+ @property
+ def annotation_string(self) -> str:
+ return "float"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return float
+
+
+class ArraySchema(SchemaBase[list[AI]], frozen=True):
+ type: Literal["array"] = "array"
+ items: SchemaObjectTypes
+ maxItems: int | None = None
+ minItems: int | None = None
+ uniqueItems: bool = False
+ const: list[AI] | None = None
+ enum: list[list[AI]] | None = None
+ nullable: bool = False
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[list[AI], ArraySchema[AI]]:
+ if self.const is not None:
+ return self.const, self
+
+ if self.enum is not None:
+ return choice(self.enum), self
+
+ minimum = self.minItems if self.minItems is not None else 0
+ maximum = self.maxItems if self.maxItems is not None else 1
+ maximum = max(minimum, maximum)
+
+ value: list[AI] = []
+ number_of_items_to_generate = randint(minimum, maximum)
+ for _ in range(number_of_items_to_generate):
+ item_value = cast("AI", self.items.get_valid_value()[0])
+ value.append(item_value)
+ return value, self
+
+ def get_values_out_of_bounds(self, current_value: list[AI]) -> list[list[AI]]:
+ invalid_values: list[list[AI]] = []
+
+ if self.minItems:
+ invalid_value = current_value[0 : self.minItems - 1]
+ invalid_values.append(invalid_value)
+
+ if self.maxItems is not None:
+ invalid_value = []
+ if not current_value:
+ current_value = self.get_valid_value()[0]
+
+ if not current_value:
+ current_value = [self.items.get_valid_value()[0]] # type: ignore[list-item]
+
+ while len(invalid_value) <= self.maxItems:
+ invalid_value.append(choice(current_value))
+ invalid_values.append(invalid_value)
+
+ if invalid_values:
+ return invalid_values
+
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> list[AI]:
+ valid_values = []
+ if self.const is not None:
+ valid_values = [self.const]
+ if self.enum is not None:
+ valid_values = self.enum
+
+ if not valid_values:
+ raise ValueError
+
+ invalid_value = []
+ for value in valid_values:
+ invalid_value.extend(value)
+ invalid_value.extend(value)
+
+ return invalid_value
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[list[AI]]
+ ) -> list[AI]:
+ values_from_constraint = deepcopy(values_from_constraint)
+
+ valid_array = values_from_constraint.pop()
+ invalid_array: list[AI] = []
+ for value in valid_array:
+ invalid_value = self.items.get_invalid_value_from_constraint(
+ values_from_constraint=[value], # type: ignore[list-item]
+ )
+ invalid_array.append(invalid_value) # type: ignore[arg-type]
+ return invalid_array
+
+ def get_invalid_data(
+ self,
+ valid_data: list[AI],
+ status_code: int,
+ invalid_property_default_code: int,
+ ) -> list[AI]:
+ """Return a data set with one of the properties set to an invalid value or type."""
+ invalid_values: list[list[AI]] = []
+
+ relations = self.relations_mapping.get_body_relations_for_error_code(
+ error_code=status_code
+ )
+ # TODO: handle relations applicable to arrays / lists
+
+ if status_code == invalid_property_default_code:
+ try:
+ values_out_of_bounds = self.get_values_out_of_bounds(
+ current_value=valid_data
+ )
+ invalid_values.extend(values_out_of_bounds)
+ except ValueError:
+ pass
+ try:
+ invalid_const_or_enum = self.get_invalid_value_from_const_or_enum()
+ invalid_values.append(invalid_const_or_enum)
+ except ValueError:
+ pass
+ if is_object_schema(self.items):
+ data_to_invalidate = deepcopy(valid_data)
+ valid_item = (
+ data_to_invalidate.pop()
+ if valid_data
+ else self.items.get_valid_value()[0]
+ )
+ invalid_item = self.items.get_invalid_data(
+ valid_data=valid_item, # type: ignore[arg-type]
+ status_code=status_code,
+ invalid_property_default_code=invalid_property_default_code,
+ )
+ invalid_data = [*data_to_invalidate, invalid_item]
+ invalid_values.append(invalid_data)
+
+ if not invalid_values:
+ raise ValueError(
+ f"No constraint can be broken to cause status_code {status_code}"
+ )
+ return choice(invalid_values)
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ if (
+ self.maxItems is not None
+ or self.minItems is not None
+ or self.uniqueItems
+ or self.const is not None
+ or self.enum is not None
+ ):
+ return True
+ if isinstance(self.items, (BooleanSchema, IntegerSchema, NumberSchema)):
+ return True
+ return False
+
+ @property
+ def annotation_string(self) -> str:
+ return f"list[{self.items.annotation_string}]"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return list
+
+
+# NOTE: Workaround for cyclic PropertiesMapping / SchemaObjectTypes annotations
+def _get_properties_mapping_default() -> PropertiesMapping:
+ return _get_empty_properties_mapping()
+
+
+class ObjectSchema(SchemaBase[dict[str, JSON]], frozen=True):
+ type: Literal["object"] = "object"
+ properties: PropertiesMapping = Field(
+ default_factory=_get_properties_mapping_default
+ )
+ additionalProperties: SchemaObjectTypes | bool = True
+ required: list[str] = []
+ maxProperties: int | None = None
+ minProperties: int | None = None
+ const: dict[str, JSON] | None = None
+ enum: list[dict[str, JSON]] | None = None
+ nullable: bool = False
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[dict[str, JSON], ObjectSchema]:
+ if self.const is not None:
+ return self.const, self
+
+ if self.enum is not None:
+ return choice(self.enum), self
+
+ json_data: dict[str, Any] = {}
+
+ property_names = self._get_property_names_to_process()
+
+ for property_name in property_names:
+ property_schema = self.properties.root[property_name]
+ if property_schema.readOnly:
+ continue
+
+ json_data[property_name] = self._get_data_for_property(
+ property_name=property_name,
+ property_schema=property_schema,
+ operation_id=operation_id,
+ )
+
+ return json_data, self
+
+ def _get_property_names_to_process(self) -> list[str]:
+ property_names = []
+
+ properties = {} if self.properties is None else self.properties.root
+ for property_name in properties:
+ # register the oas_name
+ _ = get_safe_name_for_oas_name(property_name)
+ if constrained_values := self._get_constrained_values(
+ property_name=property_name
+ ):
+ # do not add properties that are configured to be ignored
+ if IGNORE in constrained_values: # type: ignore[comparison-overlap]
+ continue
+ property_names.append(property_name)
+
+ max_properties = self.maxProperties
+ if max_properties and len(property_names) > max_properties:
+ required_properties = self.required
+ number_of_optional_properties = max_properties - len(required_properties)
+ optional_properties = [
+ name for name in property_names if name not in required_properties
+ ]
+ selected_optional_properties = sample(
+ optional_properties, number_of_optional_properties
+ )
+ property_names = required_properties + selected_optional_properties
+
+ return property_names
+
+ def _get_data_for_property(
+ self,
+ property_name: str,
+ property_schema: SchemaObjectTypes,
+ operation_id: str | None,
+ ) -> JSON:
+ if constrained_values := self._get_constrained_values(
+ property_name=property_name
+ ):
+ constrained_value = choice(constrained_values)
+ # Check if the chosen value is a nested relations_mapping; since a
+ # mapping is never instantiated, we can use isinstance(..., type) for this.
+ if isinstance(constrained_value, type):
+ property_schema.attach_relations_mapping(constrained_value)
+ valid_value, _ = property_schema.get_valid_value(
+ operation_id=operation_id
+ )
+ return valid_value
+
+ return constrained_value
+
+ if (
+ dependent_id := get_dependent_id(
+ relations_mapping=self.relations_mapping,
+ property_name=property_name,
+ operation_id=operation_id,
+ )
+ ) is not None:
+ return dependent_id
+
+ # Relations are mapped to endpoints; they are not attached to the property
+ # value schemas so update the schema before value generation
+ property_schema.attach_relations_mapping(self.relations_mapping)
+ return property_schema.get_valid_value(operation_id=operation_id)[0]
+
+ def _get_constrained_values(
+ self, property_name: str
+ ) -> list[JSON | RelationsMappingType]:
+ relations = self.relations_mapping.get_relations()
+ values_list = [
+ c.values
+ for c in relations
+ if (
+ isinstance(c, PropertyValueConstraint)
+ and c.property_name == property_name
+ )
+ ]
+ # values should be empty or contain 1 list of allowed values
+ return values_list.pop() if values_list else []
+
+ def get_values_out_of_bounds(
+ self, current_value: Mapping[str, JSON]
+ ) -> list[dict[str, JSON]]:
+ raise ValueError
+
+ def get_invalid_value_from_const_or_enum(self) -> dict[str, JSON]:
+ valid_values = []
+ if self.const is not None:
+ valid_values = [self.const]
+ if self.enum is not None:
+ valid_values = self.enum
+
+ if not valid_values:
+ raise ValueError
+
+ # This invalidation will not work for a const and may not work for
+ # an enum. In that case a different invalidation approach will be used.
+ invalid_value = {**valid_values[0]}
+ for value in valid_values:
+ for key in invalid_value.keys():
+ invalid_value[key] = value.get(key)
+ if invalid_value not in valid_values:
+ return invalid_value
+
+ raise ValueError
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[dict[str, JSON]]
+ ) -> dict[str, JSON]:
+ values_from_constraint = deepcopy(values_from_constraint)
+
+ valid_object = values_from_constraint.pop()
+ invalid_object: dict[str, JSON] = {}
+ for key, value in valid_object.items():
+ python_type_of_value = type(value)
+ json_type_of_value = json_type_name_of_python_type(python_type_of_value)
+ schema = MediaTypeObject(schema={"type": json_type_of_value}).schema_ # pyright: ignore[reportArgumentType]
+ invalid_value = schema.get_invalid_value_from_constraint( # type: ignore[union-attr]
+ values_from_constraint=[value], # type: ignore[list-item]
+ )
+ invalid_object[key] = invalid_value
+ return invalid_object
+
+ def get_invalid_data(
+ self,
+ valid_data: dict[str, JSON],
+ status_code: int,
+ invalid_property_default_code: int,
+ ) -> dict[str, JSON]:
+ """Return a data set with one of the properties set to an invalid value or type."""
+ properties: dict[str, JSON] = deepcopy(valid_data)
+
+ relations = self.relations_mapping.get_body_relations_for_error_code(
+ error_code=status_code
+ )
+ property_names = [r.property_name for r in relations]
+
+ if status_code == invalid_property_default_code:
+ # add all properties defined in the schema, including optional properties
+ property_names.extend((self.properties.root.keys()))
+ if not property_names:
+ raise ValueError(
+ f"No property can be invalidated to cause status_code {status_code}"
+ )
+ # Remove duplicates, then shuffle the property_names so different properties in
+ # the data dict are invalidated when rerunning the test.
+ shuffle(list(set(property_names)))
+ # The value of 1 property will be changed and since they are shuffled, take the first
+ property_name = property_names[0]
+ # if possible, invalidate a constraint but send otherwise valid data
+ id_dependencies = [
+ r
+ for r in relations
+ if isinstance(r, IdDependency) and r.property_name == property_name
+ ]
+ if id_dependencies:
+ invalid_id = uuid4().hex
+ logger.debug(
+ f"Breaking IdDependency for status_code {status_code}: setting "
+ f"{property_name} to {invalid_id}"
+ )
+ properties[property_name] = invalid_id
+ return properties
+
+ invalid_value_from_constraint = [
+ r.invalid_value
+ for r in relations
+ if isinstance(r, PropertyValueConstraint)
+ and r.property_name == property_name
+ and r.invalid_value_error_code == status_code
+ ]
+ if (
+ invalid_value_from_constraint
+ and invalid_value_from_constraint[0] is not NOT_SET
+ ):
+ invalid_value = invalid_value_from_constraint[0]
+ if isinstance(invalid_value, Ignore):
+ properties.pop(property_name)
+ logger.debug(
+ f"Property {property_name} removed since the invalid_value "
+ f"was IGNORE (received from get_invalid_value)"
+ )
+ else:
+ properties[property_name] = invalid_value
+ logger.debug(
+ f"Using invalid_value {invalid_value_from_constraint[0]} to "
+ f"invalidate property {property_name}"
+ )
+ return properties
+
+ value_schema = self.properties.root[property_name]
+ if isinstance(value_schema, UnionTypeSchema):
+ # Filter "type": "null" from the possible types since this indicates an
+ # optional / nullable property that can only be invalidated by sending
+ # invalid data of a non-null type
+ non_null_schemas = [
+ s
+ for s in value_schema.resolved_schemas
+ if not isinstance(s, NullSchema)
+ ]
+ value_schema = choice(non_null_schemas)
+
+ # there may not be a current_value when invalidating an optional property
+ current_value = properties.get(property_name, SENTINEL)
+ if current_value is SENTINEL:
+ current_value = value_schema.get_valid_value()[0]
+
+ values_from_constraint = [
+ r.values[0]
+ for r in relations
+ if isinstance(r, PropertyValueConstraint)
+ and r.property_name == property_name
+ ]
+
+ invalid_value = value_schema.get_invalid_value(
+ valid_value=current_value, # type: ignore[arg-type]
+ values_from_constraint=values_from_constraint,
+ )
+ if isinstance(invalid_value, Ignore):
+ properties.pop(property_name)
+ logger.debug(
+ f"Property {property_name} removed since the invalid_value "
+ f"was IGNORE (received from get_invalid_value)"
+ )
+ else:
+ properties[property_name] = invalid_value
+ logger.debug(
+ f"Property {property_name} changed to {invalid_value} "
+ f"(received from get_invalid_value)"
+ )
+ return properties
+
+ def contains_properties(self, property_names: list[str]) -> bool:
+ if self.properties is None:
+ return False # pragma: no cover
+ for property_name in property_names:
+ if property_name not in self.properties.root:
+ return False
+ return True
+
+ @property
+ def can_be_invalidated(self) -> bool:
+ if (
+ self.required
+ or self.maxProperties is not None
+ or self.minProperties is not None
+ or self.const is not None
+ or self.enum is not None
+ ):
+ return True
+ return False
+
+ @property
+ def annotation_string(self) -> str:
+ return "dict[str, JSON]"
+
+ @property
+ def python_type(self) -> builtins.type:
+ return dict
+
+
+ResolvedSchemaObjectTypes = Annotated[
+ Union[
+ ArraySchema, # type: ignore[type-arg]
+ BooleanSchema,
+ IntegerSchema,
+ NullSchema,
+ NumberSchema,
+ ObjectSchema,
+ StringSchema,
+ ],
+ Field(discriminator="type"),
+]
+
+RESOLVED_SCHEMA_CLASS_TUPLE = (
+ NullSchema,
+ BooleanSchema,
+ StringSchema,
+ IntegerSchema,
+ NumberSchema,
+ ArraySchema,
+ ObjectSchema,
+)
+
+
+class UnionTypeSchema(SchemaBase[JSON], frozen=True):
+ allOf: list["SchemaObjectTypes"] = []
+ anyOf: list["SchemaObjectTypes"] = []
+ oneOf: list["SchemaObjectTypes"] = []
+ nullable: bool = False
+
+ def get_valid_value(
+ self,
+ operation_id: str | None = None,
+ ) -> tuple[JSON, ResolvedSchemaObjectTypes]:
+ relations = (
+ self.relations_mapping.get_relations()
+ + self.relations_mapping.get_parameter_relations()
+ )
+ constrained_property_names = [relation.property_name for relation in relations]
+
+ if not constrained_property_names:
+ resolved_schemas = self.resolved_schemas
+ chosen_schema = choice(resolved_schemas)
+ return chosen_schema.get_valid_value(operation_id=operation_id)
+
+ valid_values = []
+ valid_schemas = []
+ for candidate in self.resolved_schemas:
+ if isinstance(candidate, ObjectSchema):
+ if candidate.contains_properties(constrained_property_names):
+ valid_schemas.append(candidate)
+
+ if isinstance(candidate, UnionTypeSchema):
+ candidate.attach_relations_mapping(self.relations_mapping)
+ try:
+ valid_value = candidate.get_valid_value(operation_id=operation_id)
+ valid_values.append(valid_value)
+ except ValueError:
+ pass
+ for valid_schema in valid_schemas:
+ valid_value = valid_schema.get_valid_value(operation_id=operation_id)
+ valid_values.append(valid_value)
+
+ if valid_values:
+ return choice(valid_values)
+
+ # The constraints from the parent may not be applicable, resulting in no
+ # valid_values being generated. In that case, generated a random value as normal.
+ chosen_schema = choice(self.resolved_schemas)
+ return chosen_schema.get_valid_value(operation_id=operation_id)
+
+ def get_values_out_of_bounds(self, current_value: JSON) -> list[JSON]:
+ raise ValueError
+
+ @cached_property
+ def resolved_schemas(self) -> list[ResolvedSchemaObjectTypes]:
+ schemas_to_return: list[ResolvedSchemaObjectTypes] = []
+ null_schema = None
+
+ resolved_schemas = list(self._get_resolved_schemas())
+ for schema in resolved_schemas:
+ # Prevent duplication of NullSchema when handling nullable models.
+ if isinstance(schema, NullSchema):
+ null_schema = schema
+ else:
+ schemas_to_return.append(schema)
+ if null_schema is not None:
+ schemas_to_return.append(null_schema)
+ return schemas_to_return
+
+ def _get_resolved_schemas(self) -> Generator[ResolvedSchemaObjectTypes, None, None]:
+ if self.allOf:
+ properties_list: list[PropertiesMapping] = []
+ additional_properties_list = []
+ required_list = []
+ max_properties_list = []
+ min_properties_list = []
+ nullable_list = []
+
+ schemas_to_process = []
+ for schema in self.allOf:
+ if isinstance(schema, UnionTypeSchema):
+ schemas_to_process.extend(schema.resolved_schemas)
+ else:
+ schemas_to_process.append(schema)
+
+ for schema in schemas_to_process:
+ if not isinstance(schema, ObjectSchema):
+ raise ValueError("allOf is only supported for ObjectSchemas")
+
+ if schema.const is not None:
+ raise ValueError("allOf and models with a const are not compatible")
+
+ if schema.enum:
+ raise ValueError("allOf and models with enums are not compatible")
+
+ if schema.properties.root:
+ properties_list.append(schema.properties)
+ additional_properties_list.append(schema.additionalProperties)
+ required_list += schema.required
+ max_properties_list.append(schema.maxProperties)
+ min_properties_list.append(schema.minProperties)
+ nullable_list.append(schema.nullable)
+
+ properties_dicts = [mapping.root for mapping in properties_list]
+ merged_properties = dict(ChainMap(*properties_dicts))
+
+ if True in additional_properties_list:
+ additional_properties_value: bool | SchemaObjectTypes = True
+ else:
+ additional_properties_types = []
+ for additional_properties_item in additional_properties_list:
+ if isinstance(
+ additional_properties_item, RESOLVED_SCHEMA_CLASS_TUPLE
+ ):
+ additional_properties_types.append(additional_properties_item)
+ if isinstance(additional_properties_item, UnionTypeSchema):
+ additional_properties_types.extend(
+ additional_properties_item.resolved_schemas
+ )
+ if not additional_properties_types:
+ additional_properties_value = False
+ else:
+ additional_properties_value = UnionTypeSchema(
+ anyOf=additional_properties_types,
+ )
+
+ max_properties = [max for max in max_properties_list if max is not None]
+ min_properties = [min for min in min_properties_list if min is not None]
+ max_propeties_value = max(max_properties) if max_properties else None
+ min_propeties_value = min(min_properties) if min_properties else None
+
+ merged_schema = ObjectSchema(
+ type="object",
+ properties=PropertiesMapping(root=merged_properties),
+ additionalProperties=additional_properties_value,
+ required=required_list,
+ maxProperties=max_propeties_value,
+ minProperties=min_propeties_value,
+ nullable=False,
+ )
+ merged_schema.attach_relations_mapping(self.relations_mapping)
+ yield merged_schema
+ # If all schemas are nullable the merged schema is treated as nullable.
+ if all(nullable_list):
+ null_schema = NullSchema()
+ null_schema.attach_relations_mapping(self.relations_mapping)
+ yield null_schema
+ else:
+ for schema in self.anyOf + self.oneOf:
+ if isinstance(schema, RESOLVED_SCHEMA_CLASS_TUPLE):
+ if schema.nullable:
+ schema.__dict__["nullable"] = False
+ null_schema = NullSchema()
+ null_schema.attach_relations_mapping(self.relations_mapping)
+ yield null_schema
+ yield schema
+ else:
+ yield from schema.resolved_schemas
+
+ def get_invalid_value_from_const_or_enum(self) -> JSON:
+ raise ValueError
+
+ def get_invalid_value_from_constraint(
+ self, values_from_constraint: list[JSON]
+ ) -> JSON:
+ raise ValueError
+
+ @property
+ def annotation_string(self) -> str:
+ unique_annotations = {s.annotation_string for s in self.resolved_schemas}
+ return " | ".join(unique_annotations)
+
+
+SchemaObjectTypes: TypeAlias = ResolvedSchemaObjectTypes | UnionTypeSchema
+
+
+class PropertiesMapping(RootModel[dict[str, SchemaObjectTypes]], frozen=True): ...
+
+
+def _get_empty_properties_mapping() -> PropertiesMapping:
+ return PropertiesMapping(root={})
+
+
+class ParameterObject(BaseModel):
+ name: str
+ in_: str = Field(..., alias="in")
+ required: bool = False
+ description: str = ""
+ schema_: SchemaObjectTypes | None = Field(None, alias="schema")
+ relations_mapping: RelationsMappingType | None = None
+
+ def attach_relations_mapping(self, relations_mapping: RelationsMappingType) -> None:
+ if self.schema_: # pragma: no branch
+ self.schema_.attach_relations_mapping(relations_mapping)
+
+ def replace_nullable_with_union(self) -> None:
+ if self.schema_: # pragma: no branch
+ processed_schema = nullable_schema_to_union_schema(self.schema_)
+ self.schema_ = processed_schema
+
+
+class MediaTypeObject(BaseModel):
+ schema_: SchemaObjectTypes | None = Field(None, alias="schema")
+
+
+class RequestBodyObject(BaseModel):
+ content: dict[str, MediaTypeObject]
+ required: bool = False
+ description: str = ""
+
+ @cached_property
+ def schema_(self) -> SchemaObjectTypes | None:
+ if not self.mime_type:
+ return None
+
+ if len(self._json_schemas) > 1:
+ logger.info(
+ f"Multiple JSON media types defined for requestBody, "
+ f"using the first candidate from {self.content}"
+ )
+ return self._json_schemas[self.mime_type]
+
+ @cached_property
+ def mime_type(self) -> str | None:
+ if not self._json_schemas:
+ return None
+
+ return next(iter(self._json_schemas))
+
+ @cached_property
+ def _json_schemas(self) -> dict[str, SchemaObjectTypes]:
+ json_schemas = {
+ mime_type: media_type.schema_
+ for mime_type, media_type in self.content.items()
+ if "json" in mime_type and media_type.schema_ is not None
+ }
+ return json_schemas
+
+ def attach_relations_mapping(self, relations_mapping: RelationsMappingType) -> None:
+ for media_object_type in self.content.values():
+ if media_object_type and media_object_type.schema_: # pragma: no branch
+ media_object_type.schema_.attach_relations_mapping(relations_mapping)
+
+ def replace_nullable_with_union(self) -> None:
+ for media_object_type in self.content.values():
+ if media_object_type and media_object_type.schema_: # pragma: no branch
+ processed_schema = nullable_schema_to_union_schema(
+ media_object_type.schema_
+ )
+ media_object_type.schema_ = processed_schema
+
+
+class HeaderObject(BaseModel): ...
+
+
+class LinkObject(BaseModel): ...
+
+
+class ResponseObject(BaseModel):
+ description: str
+ content: dict[str, MediaTypeObject] = {}
+ headers: dict[str, HeaderObject] = {}
+ links: dict[str, LinkObject] = {}
+
+
+class OperationObject(BaseModel):
+ operationId: str | None = None
+ summary: str = ""
+ description: str = ""
+ tags: list[str] = []
+ parameters: list[ParameterObject] = []
+ requestBody: RequestBodyObject | None = None
+ responses: dict[str, ResponseObject] = {}
+ relations_mapping: RelationsMappingType | None = None
+
+ def update_parameters(self, parameters: list[ParameterObject]) -> None:
+ self.parameters.extend(parameters)
+
+ def attach_relations_mappings(self) -> None:
+ if not self.relations_mapping:
+ return
+
+ if self.requestBody:
+ self.requestBody.attach_relations_mapping(self.relations_mapping)
+
+ for parameter_object in self.parameters:
+ parameter_object.attach_relations_mapping(self.relations_mapping)
+
+ def replace_nullable_with_union(self) -> None:
+ if self.requestBody:
+ self.requestBody.replace_nullable_with_union()
+
+ for parameter_object in self.parameters:
+ parameter_object.replace_nullable_with_union()
+
+
+class PathItemObject(BaseModel):
+ get: OperationObject | None = None
+ post: OperationObject | None = None
+ patch: OperationObject | None = None
+ put: OperationObject | None = None
+ delete: OperationObject | None = None
+ summary: str = ""
+ description: str = ""
+ parameters: list[ParameterObject] = []
+ relations_mapping: RelationsMappingType | None = None
+ id_mapper: tuple[str, Callable[[str], str]] = (
+ "id",
+ dummy_transformer,
+ )
+
+ @property
+ def operations(self) -> dict[str, OperationObject]:
+ return {
+ k: v for k, v in self.__dict__.items() if isinstance(v, OperationObject)
+ }
+
+ def update_operation_parameters(self) -> None:
+ if not self.parameters:
+ return
+
+ operations_to_update = self.operations
+ for operation_object in operations_to_update.values():
+ operation_object.update_parameters(self.parameters)
+
+ def attach_relations_mappings(self) -> None:
+ for operation_object in self.operations.values():
+ operation_object.attach_relations_mappings()
+
+ def replace_nullable_with_union(self) -> None:
+ for operation_object in self.operations.values():
+ operation_object.attach_relations_mappings()
+ operation_object.replace_nullable_with_union()
+
+
+class InfoObject(BaseModel):
+ title: str
+ version: str
+ summary: str = ""
+ description: str = ""
+
+
+class OpenApiObject(BaseModel):
+ info: InfoObject
+ paths: dict[str, PathItemObject]
+
+
+def nullable_schema_to_union_schema(schema: SchemaObjectTypes) -> SchemaObjectTypes:
+ if not schema.nullable:
+ return schema
+
+ schema.__dict__["nullable"] = False
+ null_schema = NullSchema()
+ null_schema.attach_relations_mapping(schema.relations_mapping)
+ union_schema = UnionTypeSchema(oneOf=[schema, null_schema])
+ union_schema.attach_relations_mapping(schema.relations_mapping)
+ return union_schema
+
+
+# TODO: move to keyword_logic?
+def get_dependent_id(
+ relations_mapping: RelationsMappingType | None,
+ property_name: str,
+ operation_id: str | None,
+) -> str | int | float | None:
+ relations = relations_mapping.get_relations() if relations_mapping else []
+ # multiple get paths are possible based on the operation being performed
+ id_get_paths = [
+ (d.get_path, d.operation_id)
+ for d in relations
+ if (isinstance(d, IdDependency) and d.property_name == property_name)
+ ]
+ if not id_get_paths:
+ return None
+ if len(id_get_paths) == 1:
+ id_get_path, _ = id_get_paths.pop()
+ else:
+ try:
+ [id_get_path] = [
+ path for path, operation in id_get_paths if operation == operation_id
+ ]
+ # There could be multiple get_paths, but not one for the current operation
+ except ValueError:
+ return None
+
+ valid_id = cast(
+ str | int | float,
+ run_keyword("get_valid_id_for_path", id_get_path), # pyright: ignore[reportArgumentType]
+ )
+ logger.debug(f"get_dependent_id for {id_get_path} returned {valid_id}")
+ return valid_id
diff --git a/src/OpenApiLibCore/request_data.py b/src/OpenApiLibCore/models/request_data.py
similarity index 78%
rename from src/OpenApiLibCore/request_data.py
rename to src/OpenApiLibCore/models/request_data.py
index 707539d..996dd30 100644
--- a/src/OpenApiLibCore/request_data.py
+++ b/src/OpenApiLibCore/models/request_data.py
@@ -4,17 +4,15 @@
from dataclasses import dataclass, field
from functools import cached_property
from random import sample
-from typing import Any
from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.dto_base import Dto
-from OpenApiLibCore.dto_utils import DefaultDto
-from OpenApiLibCore.models import (
+from OpenApiLibCore.models.oas_models import (
ObjectSchema,
ParameterObject,
ResolvedSchemaObjectTypes,
UnionTypeSchema,
)
+from OpenApiLibCore.protocols import RelationsMappingType
@dataclass
@@ -24,14 +22,15 @@ class RequestValues:
url: str
method: str
params: dict[str, JSON] = field(default_factory=dict)
- headers: dict[str, JSON] = field(default_factory=dict)
- json_data: dict[str, JSON] = field(default_factory=dict)
+ headers: dict[str, str] = field(default_factory=dict)
+ json_data: JSON = None
def override_body_value(self, name: str, value: JSON) -> None:
- if name in self.json_data:
+ # TODO: add support for overriding list body items
+ if isinstance(self.json_data, dict) and name in self.json_data:
self.json_data[name] = value
- def override_header_value(self, name: str, value: JSON) -> None:
+ def override_header_value(self, name: str, value: str) -> None:
if name in self.headers:
self.headers[name] = value
@@ -41,25 +40,27 @@ def override_param_value(self, name: str, value: JSON) -> None:
def override_request_value(self, name: str, value: JSON) -> None:
self.override_body_value(name=name, value=value)
- self.override_header_value(name=name, value=value)
+ self.override_header_value(name=name, value=str(value))
self.override_param_value(name=name, value=value)
def remove_parameters(self, parameters: list[str]) -> None:
for parameter in parameters:
_ = self.params.pop(parameter, None)
_ = self.headers.pop(parameter, None)
- _ = self.json_data.pop(parameter, None)
+ if isinstance(self.json_data, dict):
+ _ = self.json_data.pop(parameter, None)
@dataclass
class RequestData:
"""Helper class to manage parameters used when making requests."""
- dto: Dto | DefaultDto = field(default_factory=DefaultDto)
- body_schema: ObjectSchema | None = None
+ valid_data: JSON
+ relations_mapping: RelationsMappingType
+ body_schema: ResolvedSchemaObjectTypes | None = None
parameters: list[ParameterObject] = field(default_factory=list)
params: dict[str, JSON] = field(default_factory=dict)
- headers: dict[str, JSON] = field(default_factory=dict)
+ headers: dict[str, str] = field(default_factory=dict)
has_body: bool = True
def __post_init__(self) -> None:
@@ -69,17 +70,20 @@ def __post_init__(self) -> None:
@property
def has_optional_properties(self) -> bool:
- """Whether or not the dto data (json data) contains optional properties."""
+ """Whether or not the json data contains optional properties."""
def is_required_property(property_name: str) -> bool:
return property_name in self.required_property_names
- properties = (self.dto.as_dict()).keys()
+ if not isinstance(self.valid_data, dict):
+ return False
+
+ properties = (self.valid_data).keys()
return not all(map(is_required_property, properties))
@property
def required_property_names(self) -> list[str]:
- if self.body_schema:
+ if isinstance(self.body_schema, ObjectSchema):
return self.body_schema.required
return []
@@ -165,28 +169,38 @@ def headers_that_can_be_invalidated(self) -> set[str]:
return result
- def get_required_properties_dict(self) -> dict[str, Any]:
- """Get the json-compatible dto data containing only the required properties."""
- relations = self.dto.get_relations()
+ def get_required_properties_dict(self) -> dict[str, JSON]:
+ """Get the json data containing only the required properties."""
+ relations = self.relations_mapping.get_relations()
mandatory_properties = [
relation.property_name
for relation in relations
if getattr(relation, "treat_as_mandatory", False)
]
- required_properties = self.body_schema.required if self.body_schema else []
+ required_properties = (
+ self.body_schema.required
+ if isinstance(self.body_schema, ObjectSchema)
+ else []
+ )
required_properties.extend(mandatory_properties)
- required_properties_dict: dict[str, Any] = {}
- for key, value in (self.dto.as_dict()).items():
+ required_properties_dict: dict[str, JSON] = {}
+ if not isinstance(self.valid_data, dict):
+ return required_properties_dict
+
+ for key, value in self.valid_data.items():
if key in required_properties:
required_properties_dict[key] = value
return required_properties_dict
- def get_minimal_body_dict(self) -> dict[str, Any]:
+ def get_minimal_body_dict(self) -> dict[str, JSON]:
required_properties_dict = self.get_required_properties_dict()
min_properties = 0
- if self.body_schema and self.body_schema.minProperties is not None:
+ if (
+ isinstance(self.body_schema, ObjectSchema)
+ and self.body_schema.minProperties is not None
+ ):
min_properties = self.body_schema.minProperties
number_of_optional_properties_to_add = min_properties - len(
@@ -196,9 +210,12 @@ def get_minimal_body_dict(self) -> dict[str, Any]:
if number_of_optional_properties_to_add < 1:
return required_properties_dict
+ if not isinstance(self.valid_data, dict):
+ return required_properties_dict
+
optional_properties_dict = {
k: v
- for k, v in self.dto.as_dict().items()
+ for k, v in self.valid_data.items()
if k not in required_properties_dict
}
optional_properties_to_keep = sample(
@@ -218,7 +235,7 @@ def get_required_params(self) -> dict[str, JSON]:
k: v for k, v in self.params.items() if k in self.required_parameter_names
}
- def get_required_headers(self) -> dict[str, JSON]:
+ def get_required_headers(self) -> dict[str, str]:
"""Get the headers dict containing only the required headers."""
return {
k: v for k, v in self.headers.items() if k in self.required_parameter_names
@@ -230,7 +247,7 @@ def required_parameter_names(self) -> list[str]:
The names of the mandatory parameters, including the parameters configured to be
treated as mandatory using a PropertyValueConstraint.
"""
- relations = self.dto.get_parameter_relations()
+ relations = self.relations_mapping.get_parameter_relations()
mandatory_property_names = [
relation.property_name
for relation in relations
diff --git a/src/OpenApiLibCore/models/resource_relations.py b/src/OpenApiLibCore/models/resource_relations.py
new file mode 100644
index 0000000..9f38d36
--- /dev/null
+++ b/src/OpenApiLibCore/models/resource_relations.py
@@ -0,0 +1,63 @@
+from abc import ABC
+from dataclasses import dataclass
+from typing import Any
+
+NOT_SET = object()
+
+
+class ResourceRelation(ABC):
+ """ABC for all resource relations or restrictions within the API."""
+
+ property_name: str
+ error_code: int
+
+
+@dataclass
+class PathPropertiesConstraint(ResourceRelation):
+ """The value to be used as the ``path`` for related requests."""
+
+ path: str
+ property_name: str = "id"
+ invalid_value: Any = NOT_SET
+ invalid_value_error_code: int = 422
+ error_code: int = 404
+
+
+@dataclass
+class PropertyValueConstraint(ResourceRelation):
+ """The allowed values for property_name."""
+
+ property_name: str
+ values: list[Any]
+ invalid_value: Any = NOT_SET
+ invalid_value_error_code: int = 422
+ error_code: int = 422
+ treat_as_mandatory: bool = False
+
+
+@dataclass
+class IdDependency(ResourceRelation):
+ """The path where a valid id for the property_name can be gotten (using GET)."""
+
+ property_name: str
+ get_path: str
+ operation_id: str = ""
+ error_code: int = 422
+
+
+@dataclass
+class IdReference(ResourceRelation):
+ """The path where a resource that needs this resource's id can be created (using POST)."""
+
+ property_name: str
+ post_path: str
+ error_code: int = 422
+
+
+@dataclass
+class UniquePropertyValueConstraint(ResourceRelation):
+ """The value of the property must be unique within the resource scope."""
+
+ property_name: str
+ value: Any
+ error_code: int = 422
diff --git a/src/OpenApiLibCore/oas_cache.py b/src/OpenApiLibCore/oas_cache.py
deleted file mode 100644
index a9c4d24..0000000
--- a/src/OpenApiLibCore/oas_cache.py
+++ /dev/null
@@ -1,18 +0,0 @@
-"""Module holding the (global) parser cache."""
-
-from dataclasses import dataclass
-
-from openapi_core import Spec
-from prance import ResolvingParser
-
-from OpenApiLibCore.protocols import ResponseValidatorType
-
-
-@dataclass
-class CachedParser:
- parser: ResolvingParser
- validation_spec: Spec
- response_validator: ResponseValidatorType
-
-
-PARSER_CACHE: dict[str, CachedParser] = {}
diff --git a/src/OpenApiLibCore/openapi_libcore.libspec b/src/OpenApiLibCore/openapi_libcore.libspec
index bf710d2..395c830 100644
--- a/src/OpenApiLibCore/openapi_libcore.libspec
+++ b/src/OpenApiLibCore/openapi_libcore.libspec
@@ -1,14 +1,14 @@
-
-1.0.5
+
+2.0.0b1The OpenApiLibCore library provides the keywords and core logic to interact with an OpenAPI server.
Visit the <a href="./index.html" target="_blank">OpenApiTools documentation</a> for an introduction.
-
-
+
+source
@@ -41,8 +41,8 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
-
-invalid_property_default_response
+
+invalid_data_default_response422
@@ -120,7 +120,7 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
extra_headers
-
+
@@ -129,11 +129,11 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
cookies
-
+
-
+None
@@ -141,7 +141,7 @@ Visit the <a href="./index.html" target="_blank">OpenApiTools documentatio
proxies
-
+
@@ -189,7 +189,7 @@ by Response validation.
<h3>mappings_path</h3>
See the Advanced Use tab for an in-depth explanation.
-<h3>invalid_property_default_response</h3>
+<h3>invalid_data_default_response</h3>
The default response code for requests with a JSON body that does not comply
with the schema.
Example: a value outside the specified range or a string value
@@ -272,25 +272,22 @@ A dictionary of <code>"protocol": "proxy url"</code> to use for all
-
-
+
+href
-
+referenced_resource
-
-
-Attempt to GET the resource referenced by the <span class="name">href</span> and validate it's equal
-to the provided <span class="name">referenced_resource</span> object / dictionary.
-Attempt to GET the resource referenced by the `href` and validate it's equal to the provided `referenced_resource` object / dictionary.
+to the provided <span class="name">referenced_resource</span>.
+Attempt to GET the resource referenced by the `href` and validate it's equal to the provided `referenced_resource`.
-
+url
@@ -348,18 +345,28 @@ to the provided <span class="name">referenced_resource</span> object
-Perform a request using the security token or authentication set in the library.
-
-<span class="name">json_data</span>, <span class="name">data</span> and <span class="name">files</span> are passed to <span class="name">requests.request</span>s <span class="name">json</span>,
-<span class="name">data</span> and <span class="name">files</span> parameters unaltered.
-See the requests documentation for details:
-https://requests.readthedocs.io/en/latest/api/#requests.request
+See the <a href="#Perform%20Authorized%20Request" class="name">Perform Authorized Request</a> keyword.
-> Note: provided username / password or auth objects take precedence over token
- based security
-Perform a request using the security token or authentication set in the library.
+The difference between these keywords is that this keyword accepts separate
+arguments where <a href="#Perform%20Validated%20Request" class="name">Perform Validated Request</a> accepts a <span class="name">RequestValues</span> object
+(see the <a href="#Get%20Request%20Values" class="name">Get Request Values</a> keyword for details).
+See the `Perform Authorized Request` keyword.
-
+
+
+
+request_values
+
+
+
+
+
+
+
+Convert a RequestValues object to a dictionary.
+Convert a RequestValues object to a dictionary.
+
+url
@@ -374,7 +381,7 @@ https://requests.readthedocs.io/en/latest/api/#requests.request
is used by the resource defined by the <span class="name">resource_relation</span>.
Ensure that the (right-most) `id` of the resource referenced by the `url` is used by the resource defined by the `resource_relation`.
-
+url
@@ -388,7 +395,7 @@ is used by the resource defined by the <span class="name">resource_relatio
<span class="name">ids</span> from the response.
Perform a GET request on the `url` and return the list of resource `ids` from the response.
-
+url
@@ -407,18 +414,15 @@ is used by the resource defined by the <span class="name">resource_relatio
-
-
-
-
-Return <span class="name">json_data</span> based on the <span class="name">dto</span> on the <span class="name">request_data</span> that will cause
-the provided <span class="name">status_code</span> for the <span class="name">method</span> operation on the <span class="name">url</span>.
+
+Return <span class="name">json_data</span> based on the <span class="name">relations_mapping</span> on the <span class="name">request_data</span> that
+will cause the provided <span class="name">status_code</span> for the <span class="name">method</span> operation on the <span class="name">url</span>.
> Note: applicable UniquePropertyValueConstraint and IdReference Relations are
considered before changes to <span class="name">json_data</span> are made.
-Return `json_data` based on the `dto` on the `request_data` that will cause the provided `status_code` for the `method` operation on the `url`.
+Return `json_data` based on the `relations_mapping` on the `request_data` that will cause the provided `status_code` for the `method` operation on the `url`.
-
+status_code
@@ -436,24 +440,19 @@ the provided <span class="name">status_code</span> for the <span
-
+Returns a version of <span class="name">params, headers</span> as present on <span class="name">request_data</span> that has
been modified to cause the provided <span class="name">status_code</span>.Returns a version of `params, headers` as present on `request_data` that has been modified to cause the provided `status_code`.
-
-
+
+valid_url
-
-path
-
-
-expected_status_code
@@ -463,14 +462,14 @@ been modified to cause the provided <span class="name">status_code</spa
Return an url with all the path parameters in the <span class="name">valid_url</span> replaced by a
random UUID if no PathPropertiesConstraint is mapped for the <span class="name">"get"</span> operation
-on the mapped <a href="#type-Path" class="name">path</a> and <span class="name">expected_status_code</span>.
+on the related <a href="#type-Path" class="name">path</a> and <span class="name">expected_status_code</span>.
If a PathPropertiesConstraint is mapped, the <span class="name">invalid_value</span> is returned.
Raises: ValueError if the valid_url cannot be invalidated.
-Return an url with all the path parameters in the `valid_url` replaced by a random UUID if no PathPropertiesConstraint is mapped for the `"get"` operation on the mapped `path` and `expected_status_code`. If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.
+Return an url with all the path parameters in the `valid_url` replaced by a random UUID if no PathPropertiesConstraint is mapped for the `"get"` operation on the related `path` and `expected_status_code`. If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.
-
-
+
+url
@@ -479,9 +478,18 @@ Raises: ValueError if the valid_url cannot be invalidated.
method
-
-dto
-
+
+json_data
+
+
+
+
+
+
+relations_mapping
+
+
+conflict_status_code
@@ -493,11 +501,11 @@ Raises: ValueError if the valid_url cannot be invalidated.
Return <span class="name">json_data</span> based on the <span class="name">UniquePropertyValueConstraint</span> that must be
-returned by the <span class="name">get_relations</span> implementation on the <span class="name">dto</span> for the given
-<span class="name">conflict_status_code</span>.
-Return `json_data` based on the `UniquePropertyValueConstraint` that must be returned by the `get_relations` implementation on the `dto` for the given `conflict_status_code`.
+returned by the <span class="name">get_relations</span> implementation on the <span class="name">relations_mapping</span> for
+the given <span class="name">conflict_status_code</span>.
+Return `json_data` based on the `UniquePropertyValueConstraint` that must be returned by the `get_relations` implementation on the `relations_mapping` for the given `conflict_status_code`.
-
+url
@@ -508,7 +516,7 @@ returned by the <span class="name">get_relations</span> implementati
Return the path as found in the <span class="name">paths</span> section based on the given <span class="name">url</span>.Return the path as found in the `paths` section based on the given `url`.
-
+path
@@ -523,7 +531,7 @@ returned by the <span class="name">get_relations</span> implementati
Return an object with valid request data for body, headers and query params.Return an object with valid request data for body, headers and query params.
-
+path
@@ -535,7 +543,7 @@ returned by the <span class="name">get_relations</span> implementati
overrides
-
+
@@ -543,10 +551,58 @@ returned by the <span class="name">get_relations</span> implementati
-Return an object with all (valid) request values needed to make a request.
+Return an object with all (valid) request values needed to make a request.
+
+The <span class="name">overrides</span> dictionary can be used to pass specific values for parameters
+instead of having them be generated automatically.Return an object with all (valid) request values needed to make a request.
-
+
+
+
+url
+
+
+
+method
+
+
+
+params
+
+
+
+
+{}
+
+
+headers
+
+
+
+
+{}
+
+
+json_data
+
+
+
+
+None
+
+
+
+This keyword can be used to instantiate a RequestValues object that can be used
+with the <a href="#Perform%20Authorized%20Request" class="name">Perform Authorized Request</a> and <a href="#Perform%20Validated%20Request" class="name">Perform Validated Request</a> keywords.
+
+This is a utility keyword that can make certain test cases or keywords more
+concise, but logging and debugging information will be more limited.
+
+See also the <a href="#Get%20Request%20Values" class="name">Get Request Values</a> keyword.
+This keyword can be used to instantiate a RequestValues object that can be used with the `Perform Authorized Request` and `Perform Validated Request` keywords.
+
+path
@@ -564,7 +620,7 @@ To prevent resource conflicts with other test cases, a new resource is created
(by a POST operation) if possible.
Support keyword that returns the `id` for an existing resource at `path`.
-
+path
@@ -582,7 +638,44 @@ keyword will be executed to retrieve valid ids for the path parameters.
[https://marketsquare.github.io/robotframework-openapitools/advanced_use.html | here].
This keyword returns a valid url for the given `path`.
-
+
+
+
+request_values
+
+
+
+data
+
+
+
+
+None
+
+
+files
+
+
+
+
+None
+
+
+
+Perform a request using the security token or authentication set in the library.
+
+<span class="name">json_data</span>, <span class="name">data</span> and <span class="name">files</span> are passed to <span class="name">requests.request</span>s <span class="name">json</span>,
+<span class="name">data</span> and <span class="name">files</span> parameters unaltered.
+See the requests documentation for details:
+https://requests.readthedocs.io/en/latest/api/#requests.request
+
+See also <a href="#Authorized%20Request" class="name">Authorized Request</a> and <a href="#Get%20Request%20Values" class="name">Get Request Values</a>.
+
+> Note: provided username / password or auth objects take precedence over token
+ based security
+Perform a request using the security token or authentication set in the library.
+
+path
@@ -598,7 +691,7 @@ keyword will be executed to retrieve valid ids for the path parameters.
original_data
-
+
@@ -607,10 +700,12 @@ keyword will be executed to retrieve valid ids for the path parameters.
This keyword first calls the Authorized Request keyword, then the Validate
Response keyword and finally validates, for <span class="name">DELETE</span> operations, whether
-the target resource was indeed deleted (OK response) or not (error responses).
+the target resource was indeed deleted (OK response) or not (error responses).
+
+See also <a href="#Validated%20Request" class="name">Validated Request</a> and <a href="#Get%20Request%20Values" class="name">Get Request Values</a>.
This keyword first calls the Authorized Request keyword, then the Validate Response keyword and finally validates, for `DELETE` operations, whether the target resource was indeed deleted (OK response) or not (error responses).
-
+auth
@@ -623,7 +718,7 @@ After calling this keyword, subsequent requests
will use the provided <span class="name">auth</span> instance.
Set the `auth` used for authentication after the library is imported.
-
+username
@@ -641,7 +736,7 @@ After calling this keyword, subsequent requests
will use the provided credentials.
Set the `username` and `password` used for basic authentication after the library is imported.
-
+extra_headers
@@ -657,7 +752,7 @@ After calling this keyword, subsequent requests
will use the provided <span class="name">extra_headers</span>.
Set the `extra_headers` used in requests after the library is imported.
-
+origin
@@ -674,7 +769,7 @@ In combination with OpenApiLibCore, the <span class="name">origin</span
target another server that hosts an API that complies to the same OAS.
Set the `origin` after the library is imported.
-
+security_token
@@ -686,8 +781,8 @@ target another server that hosts an API that complies to the same OAS.
After calling this keyword, subsequent requests will use the provided token.
Set the `security_token` after the library is imported.
-
-
+
+path
@@ -696,12 +791,9 @@ After calling this keyword, subsequent requests will use the provided token.response
-
+original_data
-
-
-{}
@@ -715,18 +807,18 @@ After calling this keyword, subsequent requests will use the provided token.
Validate the `response` by performing the following validations: - validate the `response` against the openapi schema for the `path` - validate that the response does not contain extra properties - validate that a href, if present, refers to the correct resource - validate that the value for a property that is in the response is equal to the property value that was send - validate that no `original_data` is preserved when performing a PUT operation - validate that a PATCH operation only updates the provided properties
-
+response
-Validate the <span class="name">response</span> against the OpenAPI Spec that is
+Validate the <span class="name">response</span> against the OpenAPI spec that is
loaded during library initialization.
-Validate the `response` against the OpenAPI Spec that is loaded during library initialization.
+Validate the `response` against the OpenAPI spec that is loaded during library initialization.
-
+response
@@ -734,7 +826,7 @@ loaded during library initialization.
original_data
-
+
@@ -747,6 +839,64 @@ In case a PATCH request, validate that only the properties that were patched
have changed and that other properties are still at their pre-patch values.
Validate that each property that was send that is in the response has the value that was send. In case a PATCH request, validate that only the properties that were patched have changed and that other properties are still at their pre-patch values.
+
+
+
+path
+
+
+
+status_code
+
+
+
+url
+
+
+
+method
+
+
+
+params
+
+
+
+
+{}
+
+
+headers
+
+
+
+
+{}
+
+
+json_data
+
+
+
+
+None
+
+
+original_data
+
+
+
+
+{}
+
+
+See the <a href="#Perform%20Validated%20Request" class="name">Perform Validated Request</a> keyword.
+
+The difference between these keywords is that this keyword accepts separate
+arguments where <a href="#Perform%20Validated%20Request" class="name">Perform Validated Request</a> accepts a <span class="name">RequestValues</span> object
+(see the <a href="#Get%20Request%20Values" class="name">Get Request Values</a> keyword for details).
+See the `Perform Validated Request` keyword.
+
@@ -756,10 +906,11 @@ have changed and that other properties are still at their pre-patch values.
Authorized Request
+Perform Authorized Request
-<p>Strings <code>TRUE</code>, <code>YES</code>, <code>ON</code> and <code>1</code> are converted to Boolean <code>True</code>, the empty string as well as strings <code>FALSE</code>, <code>NO</code>, <code>OFF</code> and <code>0</code> are converted to Boolean <code>False</code>, and the string <code>NONE</code> is converted to the Python <code>None</code> object. Other strings and other accepted values are passed as-is, allowing keywords to handle them specially if needed. All string comparisons are case-insensitive.</p>
+<p>Strings <code>TRUE</code>, <code>YES</code>, <code>ON</code>, <code>1</code> and possible localization specific "true strings" are converted to Boolean <code>True</code>, the empty string, strings <code>FALSE</code>, <code>NO</code>, <code>OFF</code> and <code>0</code> and possibly localization specific "false strings" are converted to Boolean <code>False</code>, and the string <code>NONE</code> is converted to the Python <code>None</code> object. Other strings and all other values are passed as-is, allowing keywords to handle them specially if needed. All string comparisons are case-insensitive.</p>
<p>Examples: <code>TRUE</code> (converted to <code>True</code>), <code>off</code> (converted to <code>False</code>), <code>example</code> (used as-is)</p>string
@@ -772,7 +923,8 @@ have changed and that other properties are still at their pre-patch values.
-<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#dict">dictionary</a> literals. They are converted to actual dictionaries using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including dictionaries and other containers.</p>
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#dict">dictionary</a> literals. They are converted to actual dictionaries using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including dictionaries and other collections.</p>
+<p>Any mapping is accepted and converted to a <code>dict</code>.</p>
<p>If the type has nested types like <code>dict[str, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
<p>Examples: <code>{'a': 1, 'b': 2}</code>, <code>{'key': 1, 'nested': {'key': 2}}</code></p>
@@ -780,22 +932,18 @@ have changed and that other properties are still at their pre-patch values.Mapping
-__init__
-Assert Href To Resource Is ValidAuthorized Request
-Get Invalid Body Data
+Convert Request Values To DictGet Invalidated ParametersGet Json Data With Conflict
-Get Request Values
-Perform Validated Request
+Get Request Values ObjectSet Extra Headers
-Validate Response
-Validate Send Response
+Validated Request<p>Conversion is done using Python's <a href="https://docs.python.org/library/functions.html#float">float</a> built-in function.</p>
-<p>Starting from RF 4.1, spaces and underscores can be used as visual separators for digit grouping purposes.</p>
+<p>Spaces and underscores can be used as visual separators for digit grouping purposes.</p>
<p>Examples: <code>3.14</code>, <code>2.9979e8</code>, <code>10 000.000 01</code></p>string
@@ -807,8 +955,7 @@ have changed and that other properties are still at their pre-patch values.
<p>Conversion is done using Python's <a href="https://docs.python.org/library/functions.html#int">int</a> built-in function. Floating point numbers are accepted only if they can be represented as integers exactly. For example, <code>1.0</code> is accepted and <code>1.1</code> is not.</p>
-<p>Starting from RF 4.1, it is possible to use hexadecimal, octal and binary numbers by prefixing values with <code>0x</code>, <code>0o</code> and <code>0b</code>, respectively.</p>
-<p>Starting from RF 4.1, spaces and underscores can be used as visual separators for digit grouping purposes.</p>
+<p>It is possible to use hexadecimal, octal and binary numbers by prefixing values with <code>0x</code>, <code>0o</code> and <code>0b</code>, respectively. Spaces and underscores can be used as visual separators for digit grouping purposes.</p>
<p>Examples: <code>42</code>, <code>-1</code>, <code>0b1010</code>, <code>10 000 000</code>, <code>0xBAD_C0FFEE</code></p>string
@@ -822,12 +969,15 @@ have changed and that other properties are still at their pre-patch values.Get Json Data With Conflict
Get Valid Id For PathPerform Validated Request
+Validated Request
-<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#list">list</a> literals. They are converted to actual lists using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including lists and other containers.</p>
-<p>If the type has nested types like <code>list[int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
-<p>Examples: <code>['one', 'two']</code>, <code>[('one', 1), ('two', 2)]</code></p>
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#list">list</a> or <a href="https://docs.python.org/library/stdtypes.html#tuple">tuple</a> literals. They are converted using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function and possible tuples converted further to lists. They can contain any values <code>ast.literal_eval</code> supports, including lists and other collections.</p>
+<p>If the argument is a list, it is used without conversion. Tuples and other sequences are converted to lists.</p>
+<p>If the type has nested types like <code>list[int]</code>, items are converted to those types automatically.</p>
+<p>Examples: <code>['one', 'two']</code>, <code>[('one', 1), ('two', 2)]</code></p>
+<p>Support to convert nested types is new in Robot Framework 6.0. Support for tuple literals is new in Robot Framework 7.4.</p>stringSequence
@@ -837,14 +987,35 @@ have changed and that other properties are still at their pre-patch values.Get Ids From Url
+
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#dict">dictionary</a> literals. They are converted to actual dictionaries using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including dictionaries and other collections.</p>
+<p>Any mapping is accepted without conversion. An exception is that if the type is <code>MutableMapping</code>, immutable values are converted to <code>dict</code>.</p>
+<p>If the type has nested types like <code>Mapping[str, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
+<p>Examples: <code>{'a': 1, 'b': 2}</code>, <code>{'key': 1, 'nested': {'key': 2}}</code></p>
+
+string
+Mapping
+
+
+__init__
+Get Request Values
+Perform Validated Request
+Validate Send Response
+Validated Request
+
+
-<p>String <code>NONE</code> (case-insensitive) is converted to Python <code>None</code> object. Other values cause an error.</p>
+<p>String <code>NONE</code> (case-insensitive) and the empty string are converted to the Python <code>None</code> object. Other values cause an error.</p>
+<p>Converting the empty string is new in Robot Framework 7.4.</p>string__init__Authorized Request
+Get Request Values Object
+Perform Authorized Request
+Validated Request
@@ -859,7 +1030,9 @@ have changed and that other properties are still at their pre-patch values.
-<p>All arguments are converted to Unicode strings.</p>
+<p>All arguments are converted to Unicode strings.</p>
+<p>Most values are converted simply by using <code>str(value)</code>. An exception is that bytes are mapped directly to Unicode code points with same ordinals. This means that, for example, <code>b"hyv\xe4"</code> becomes <code>"hyvä"</code>.</p>
+<p>Converting bytes specially is new Robot Framework 7.4.</p>Any
@@ -867,6 +1040,7 @@ have changed and that other properties are still at their pre-patch values.__init__
Assert Href To Resource Is ValidAuthorized Request
+Convert Request Values To DictEnsure In UseGet Ids From UrlGet Invalid Body Data
@@ -876,6 +1050,7 @@ have changed and that other properties are still at their pre-patch values.Get Parameterized Path From Url
Get Request DataGet Request Values
+Get Request Values ObjectGet Valid Id For PathGet Valid UrlPerform Validated Request
@@ -885,12 +1060,15 @@ have changed and that other properties are still at their pre-patch values.Set Security Token
Validate ResponseValidate Send Response
+Validated Request
-<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#tuple">tuple</a> literals. They are converted to actual tuples using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function. They can contain any values <code>ast.literal_eval</code> supports, including tuples and other containers.</p>
-<p>If the type has nested types like <code>tuple[str, int, int]</code>, items are converted to those types automatically. This in new in Robot Framework 6.0.</p>
-<p>Examples: <code>('one', 'two')</code>, <code>(('one', 1), ('two', 2))</code></p>
+<p>Strings must be Python <a href="https://docs.python.org/library/stdtypes.html#tuple">tuple</a> or <a href="https://docs.python.org/library/stdtypes.html#list">list</a> literals. They are converted using the <a href="https://docs.python.org/library/ast.html#ast.literal_eval">ast.literal_eval</a> function and possible lists converted further to tuples. They can contain any values <code>ast.literal_eval</code> supports, including tuples and other collections.</p>
+<p>If the argument is a tuple, it is used without conversion. Lists and other sequences are converted to tuples.</p>
+<p>If the type has nested types like <code>tuple[str, int, int]</code>, items are converted to those types automatically.</p>
+<p>Examples: <code>('one', 'two')</code>, <code>(('one', 1), ('two', 2))</code></p>
+<p>Support to convert nested types is new in Robot Framework 6.0. Support for list literals is new in Robot Framework 7.4.</p>stringSequence
diff --git a/src/OpenApiLibCore/openapi_libcore.py b/src/OpenApiLibCore/openapi_libcore.py
index 36ce83c..47eb281 100644
--- a/src/OpenApiLibCore/openapi_libcore.py
+++ b/src/OpenApiLibCore/openapi_libcore.py
@@ -1,13 +1,15 @@
import json as _json
import sys
+import tempfile
from collections.abc import Mapping, MutableMapping
from copy import deepcopy
from functools import cached_property
from pathlib import Path
from types import MappingProxyType
-from typing import Any, Generator
+from typing import Any, Callable, Generator, Literal, Sequence, overload
-from openapi_core import Config, OpenAPI, Spec
+from jsonschema_path import SchemaPath
+from openapi_core import Config, OpenAPI
from openapi_core.validation.exceptions import ValidationError
from prance import ResolvingParser
from prance.util.url import ResolutionError
@@ -19,32 +21,33 @@
from robot.api.exceptions import FatalError
from robot.libraries.BuiltIn import BuiltIn
-import OpenApiLibCore.data_generation as _data_generation
-import OpenApiLibCore.data_invalidation as _data_invalidation
-import OpenApiLibCore.path_functions as _path_functions
-import OpenApiLibCore.path_invalidation as _path_invalidation
-import OpenApiLibCore.resource_relations as _resource_relations
-import OpenApiLibCore.validation as _validation
+import OpenApiLibCore.data_generation.data_generation_core as _data_generation
+import OpenApiLibCore.data_generation.data_invalidation as _data_invalidation
+import OpenApiLibCore.keyword_logic.path_functions as _path_functions
+import OpenApiLibCore.keyword_logic.path_invalidation as _path_invalidation
+import OpenApiLibCore.keyword_logic.resource_relations as _resource_relations
+import OpenApiLibCore.keyword_logic.validation as _validation
from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.dto_base import Dto, IdReference
-from OpenApiLibCore.dto_utils import (
- DEFAULT_ID_PROPERTY_NAME,
- get_dto_class,
+from OpenApiLibCore.data_generation.localized_faker import FAKE
+from OpenApiLibCore.data_relations.relations_base import (
+ RelationsMapping,
get_id_property_name,
- get_path_dto_class,
+ get_path_mapping_dict,
+ get_relations_mapping_dict,
)
-from OpenApiLibCore.localized_faker import FAKE
-from OpenApiLibCore.models import (
+from OpenApiLibCore.models.oas_models import (
OpenApiObject,
+ ParameterObject,
PathItemObject,
)
-from OpenApiLibCore.oas_cache import PARSER_CACHE, CachedParser
-from OpenApiLibCore.parameter_utils import (
+from OpenApiLibCore.models.request_data import RequestData, RequestValues
+from OpenApiLibCore.models.resource_relations import IdReference
+from OpenApiLibCore.protocols import IResponseValidator
+from OpenApiLibCore.utils.oas_cache import SPEC_CACHE, CachedSpec
+from OpenApiLibCore.utils.parameter_utils import (
get_oas_name_from_safe_name,
- register_path_parameters,
+ get_safe_name_for_oas_name,
)
-from OpenApiLibCore.protocols import ResponseValidatorType
-from OpenApiLibCore.request_data import RequestData, RequestValues
from openapitools_docs.docstrings import (
OPENAPILIBCORE_INIT_DOCSTRING,
OPENAPILIBCORE_LIBRARY_DOCSTRING,
@@ -55,7 +58,40 @@
default_json_mapping: Mapping[str, JSON] = MappingProxyType({})
-@library(scope="SUITE", doc_format="HTML")
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_valid_url"], *args: str
+) -> str: ... # pragma: no cover
+
+
+@overload
+def _run_keyword(
+ keyword_name: Literal["get_request_data"], *args: str
+) -> RequestData: ... # pragma: no cover
+
+
+def _run_keyword(keyword_name: str, *args: object) -> object:
+ return run_keyword(keyword_name, *args) # pyright: ignore[reportArgumentType]
+
+
+class LibrarySearchOrderManager:
+ ROBOT_LISTENER_API_VERSION = 2
+ ROBOT_LIBRARY_SCOPE = "SUITE"
+
+ def __init__(self) -> None:
+ self.previous_search_order: dict[str, Sequence[str]] = {}
+
+ def start_keyword(self, name: str, attrs: dict[str, str]) -> None:
+ previous_search_order = BuiltIn().set_library_search_order(attrs["libname"])
+ self.previous_search_order[name] = previous_search_order
+
+ def end_keyword(self, name: str, attrs: dict[str, str]) -> None:
+ previous_search_order = self.previous_search_order.get(name, None)
+ if previous_search_order is not None:
+ _ = BuiltIn().set_library_search_order(*previous_search_order)
+
+
+@library(scope="SUITE", doc_format="HTML", listener=LibrarySearchOrderManager())
class OpenApiLibCore: # pylint: disable=too-many-public-methods
def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
self,
@@ -65,7 +101,7 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
response_validation: _validation.ValidationLevel = _validation.ValidationLevel.WARN,
disable_server_validation: bool = True,
mappings_path: str | Path = "",
- invalid_property_default_response: int = 422,
+ invalid_data_default_response: int = 422,
default_id_property_name: str = "id",
faker_locale: str | list[str] = "",
require_body_for_invalid_url: bool = False,
@@ -104,7 +140,12 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
self.extra_headers = extra_headers
self.cookies = cookies
self.proxies = proxies
- self.invalid_property_default_response = invalid_property_default_response
+ self.invalid_data_default_response = invalid_data_default_response
+ if faker_locale:
+ FAKE.set_locale(locale=faker_locale)
+ self.require_body_for_invalid_url = require_body_for_invalid_url
+ self._server_validation_warning_logged = False
+
if mappings_path and str(mappings_path) != ".":
mappings_path = Path(mappings_path)
if not mappings_path.is_file():
@@ -114,30 +155,28 @@ def __init__( # noqa: PLR0913, pylint: disable=dangerous-default-value
mappings_folder = str(mappings_path.parent)
sys.path.append(mappings_folder)
mappings_module_name = mappings_path.stem
- self.get_dto_class = get_dto_class(
+ self.relations_mapping_dict = get_relations_mapping_dict(
mappings_module_name=mappings_module_name
)
- self.get_path_dto_class = get_path_dto_class(
+ self.path_mapping_dict = get_path_mapping_dict(
mappings_module_name=mappings_module_name
)
self.get_id_property_name = get_id_property_name(
- mappings_module_name=mappings_module_name
+ mappings_module_name=mappings_module_name,
+ default_id_property_name=default_id_property_name,
)
sys.path.pop()
else:
- self.get_dto_class = get_dto_class(mappings_module_name="no mapping")
- self.get_path_dto_class = get_path_dto_class(
+ self.relations_mapping_dict = get_relations_mapping_dict(
mappings_module_name="no mapping"
)
- self.get_id_property_name = get_id_property_name(
+ self.path_mapping_dict = get_path_mapping_dict(
mappings_module_name="no mapping"
)
- if faker_locale:
- FAKE.set_locale(locale=faker_locale)
- self.require_body_for_invalid_url = require_body_for_invalid_url
- # update the globally available DEFAULT_ID_PROPERTY_NAME to the provided value
- DEFAULT_ID_PROPERTY_NAME.id_property_name = default_id_property_name
- self._server_validation_warning_logged = False
+ self.get_id_property_name = get_id_property_name(
+ mappings_module_name="no mapping",
+ default_id_property_name=default_id_property_name,
+ )
# region: library configuration keywords
@keyword
@@ -204,15 +243,20 @@ def get_request_values(
method: str,
overrides: Mapping[str, JSON] = default_json_mapping,
) -> RequestValues:
- """Return an object with all (valid) request values needed to make a request."""
- json_data: dict[str, JSON] = {}
+ """
+ Return an object with all (valid) request values needed to make a request.
- url: str = run_keyword("get_valid_url", path)
- request_data: RequestData = run_keyword("get_request_data", path, method)
+ The `overrides` dictionary can be used to pass specific values for parameters
+ instead of having them be generated automatically.
+ """
+ json_data: JSON = {}
+
+ url = _run_keyword("get_valid_url", path)
+ request_data = _run_keyword("get_request_data", path, method)
params = request_data.params
headers = request_data.headers
if request_data.has_body:
- json_data = request_data.dto.as_dict()
+ json_data = request_data.valid_data
request_values = RequestValues(
url=url,
@@ -229,7 +273,9 @@ def get_request_values(
if location == "body":
request_values.override_body_value(name=oas_name, value=value)
if location == "header":
- request_values.override_header_value(name=oas_name, value=value)
+ request_values.override_header_value(
+ name=oas_name, value=str(value)
+ )
if location == "query":
request_values.override_param_value(name=oas_name, value=str(value))
else:
@@ -244,8 +290,6 @@ def get_request_data(self, path: str, method: str) -> RequestData:
return _data_generation.get_request_data(
path=path,
method=method,
- get_dto_class=self.get_dto_class,
- get_id_property_name=self.get_id_property_name,
openapi_spec=self.openapi_spec,
)
@@ -256,10 +300,10 @@ def get_invalid_body_data(
method: str,
status_code: int,
request_data: RequestData,
- ) -> dict[str, JSON]:
+ ) -> JSON:
"""
- Return `json_data` based on the `dto` on the `request_data` that will cause
- the provided `status_code` for the `method` operation on the `url`.
+ Return `json_data` based on the `relations_mapping` on the `request_data` that
+ will cause the provided `status_code` for the `method` operation on the `url`.
> Note: applicable UniquePropertyValueConstraint and IdReference Relations are
considered before changes to `json_data` are made.
@@ -269,7 +313,7 @@ def get_invalid_body_data(
method=method,
status_code=status_code,
request_data=request_data,
- invalid_property_default_response=self.invalid_property_default_response,
+ invalid_data_default_response=self.invalid_data_default_response,
)
@keyword
@@ -277,7 +321,7 @@ def get_invalidated_parameters(
self,
status_code: int,
request_data: RequestData,
- ) -> tuple[dict[str, JSON], dict[str, JSON]]:
+ ) -> tuple[dict[str, JSON], dict[str, str]]:
"""
Returns a version of `params, headers` as present on `request_data` that has
been modified to cause the provided `status_code`.
@@ -285,23 +329,29 @@ def get_invalidated_parameters(
return _data_invalidation.get_invalidated_parameters(
status_code=status_code,
request_data=request_data,
- invalid_property_default_response=self.invalid_property_default_response,
+ invalid_data_default_response=self.invalid_data_default_response,
)
@keyword
def get_json_data_with_conflict(
- self, url: str, method: str, dto: Dto, conflict_status_code: int
+ self,
+ url: str,
+ method: str,
+ json_data: dict[str, JSON],
+ relations_mapping: type[RelationsMapping],
+ conflict_status_code: int,
) -> dict[str, JSON]:
"""
Return `json_data` based on the `UniquePropertyValueConstraint` that must be
- returned by the `get_relations` implementation on the `dto` for the given
- `conflict_status_code`.
+ returned by the `get_relations` implementation on the `relations_mapping` for
+ the given `conflict_status_code`.
"""
return _data_invalidation.get_json_data_with_conflict(
url=url,
base_url=self.base_url,
method=method,
- dto=dto,
+ json_data=json_data,
+ relations_mapping=relations_mapping, # FIXME: the model should have this information
conflict_status_code=conflict_status_code,
)
@@ -322,7 +372,6 @@ def get_valid_url(self, path: str) -> str:
return _path_functions.get_valid_url(
path=path,
base_url=self.base_url,
- get_path_dto_class=self.get_path_dto_class,
openapi_spec=self.openapi_spec,
)
@@ -335,7 +384,7 @@ def get_valid_id_for_path(self, path: str) -> str | int | float:
(by a POST operation) if possible.
"""
return _path_functions.get_valid_id_for_path(
- path=path, get_id_property_name=self.get_id_property_name
+ path=path, openapi_spec=self.openapi_spec
)
@keyword
@@ -358,30 +407,26 @@ def get_ids_from_url(self, url: str) -> list[str]:
Perform a GET request on the `url` and return the list of resource
`ids` from the response.
"""
- return _path_functions.get_ids_from_url(
- url=url, get_id_property_name=self.get_id_property_name
- )
+ return _path_functions.get_ids_from_url(url=url, openapi_spec=self.openapi_spec)
@keyword
def get_invalidated_url(
self,
valid_url: str,
- path: str = "",
expected_status_code: int = 404,
) -> str:
"""
Return an url with all the path parameters in the `valid_url` replaced by a
random UUID if no PathPropertiesConstraint is mapped for the `"get"` operation
- on the mapped `path` and `expected_status_code`.
+ on the related `path` and `expected_status_code`.
If a PathPropertiesConstraint is mapped, the `invalid_value` is returned.
Raises: ValueError if the valid_url cannot be invalidated.
"""
return _path_invalidation.get_invalidated_url(
valid_url=valid_url,
- path=path,
base_url=self.base_url,
- get_path_dto_class=self.get_path_dto_class,
+ openapi_spec=self.openapi_spec,
expected_status_code=expected_status_code,
)
@@ -414,15 +459,11 @@ def authorized_request( # pylint: disable=too-many-arguments
files: Any = None,
) -> Response:
"""
- Perform a request using the security token or authentication set in the library.
+ See the `Perform Authorized Request` keyword.
- `json_data`, `data` and `files` are passed to `requests.request`s `json`,
- `data` and `files` parameters unaltered.
- See the requests documentation for details:
- https://requests.readthedocs.io/en/latest/api/#requests.request
-
- > Note: provided username / password or auth objects take precedence over token
- based security
+ The difference between these keywords is that this keyword accepts separate
+ arguments where `Perform Validated Request` accepts a `RequestValues` object
+ (see the `Get Request Values` keyword for details).
"""
headers = deepcopy(headers) if headers else {}
if self.extra_headers:
@@ -449,8 +490,120 @@ def authorized_request( # pylint: disable=too-many-arguments
logger.debug(f"Response text: {response.text}")
return response
+ @keyword
+ def perform_authorized_request(
+ self,
+ request_values: RequestValues,
+ data: Any = None,
+ files: Any = None,
+ ) -> Response:
+ """
+ Perform a request using the security token or authentication set in the library.
+
+ `json_data`, `data` and `files` are passed to `requests.request`s `json`,
+ `data` and `files` parameters unaltered.
+ See the requests documentation for details:
+ https://requests.readthedocs.io/en/latest/api/#requests.request
+
+ See also `Authorized Request` and `Get Request Values`.
+
+ > Note: provided username / password or auth objects take precedence over token
+ based security
+ """
+ return self.authorized_request(
+ url=request_values.url,
+ method=request_values.method,
+ params=request_values.params,
+ headers=request_values.headers,
+ json_data=request_values.json_data,
+ data=data,
+ files=files,
+ )
+
+ @keyword
+ def get_request_values_object(
+ self,
+ url: str,
+ method: str,
+ params: dict[str, JSON] = {},
+ headers: dict[str, str] = {},
+ json_data: JSON = None,
+ ) -> RequestValues:
+ """
+ This keyword can be used to instantiate a RequestValues object that can be used
+ with the `Perform Authorized Request` and `Perform Validated Request` keywords.
+
+ This is a utility keyword that can make certain test cases or keywords more
+ concise, but logging and debugging information will be more limited.
+
+ See also the `Get Request Values` keyword.
+ """
+ # RequestValues has methods that perform mutations on its values, so
+ # deepcopy to prevent mutation by reference.
+ params = deepcopy(params) if params else {}
+ headers = deepcopy(headers) if headers else {}
+ json_data = deepcopy(json_data)
+ return RequestValues(
+ url=url,
+ method=method,
+ params=params,
+ headers=headers,
+ json_data=json_data,
+ )
+
+ @keyword
+ def convert_request_values_to_dict(
+ self, request_values: RequestValues
+ ) -> dict[str, JSON]:
+ """Convert a RequestValues object to a dictionary."""
+ return {
+ "url": request_values.url,
+ "method": request_values.method,
+ "params": request_values.params,
+ "headers": request_values.headers,
+ "json_data": request_values.json_data,
+ }
+
# endregion
# region: validation keywords
+ @keyword
+ def validated_request(
+ self,
+ path: str,
+ status_code: int,
+ url: str,
+ method: str,
+ params: dict[str, JSON] = {},
+ headers: dict[str, str] = {},
+ json_data: JSON = None,
+ original_data: Mapping[str, JSON] = default_json_mapping,
+ ) -> None:
+ """
+ See the `Perform Validated Request` keyword.
+
+ The difference between these keywords is that this keyword accepts separate
+ arguments where `Perform Validated Request` accepts a `RequestValues` object
+ (see the `Get Request Values` keyword for details).
+ """
+ # RequestValues has methods that perform mutations on its values, so
+ # deepcopy to prevent mutation by reference.
+ params = deepcopy(params) if params else {}
+ headers = deepcopy(headers) if headers else {}
+ json_data = deepcopy(json_data)
+ request_values = RequestValues(
+ url=url,
+ method=method,
+ params=params,
+ headers=headers,
+ json_data=json_data,
+ )
+ _validation.perform_validated_request(
+ path=path,
+ status_code=status_code,
+ request_values=request_values,
+ original_data=original_data,
+ )
+
@keyword
def perform_validated_request(
self,
@@ -463,6 +616,8 @@ def perform_validated_request(
This keyword first calls the Authorized Request keyword, then the Validate
Response keyword and finally validates, for `DELETE` operations, whether
the target resource was indeed deleted (OK response) or not (error responses).
+
+ See also `Validated Request` and `Get Request Values`.
"""
_validation.perform_validated_request(
path=path,
@@ -474,7 +629,7 @@ def perform_validated_request(
@keyword
def validate_response_using_validator(self, response: Response) -> None:
"""
- Validate the `response` against the OpenAPI Spec that is
+ Validate the `response` against the OpenAPI spec that is
loaded during library initialization.
"""
_validation.validate_response_using_validator(
@@ -484,11 +639,11 @@ def validate_response_using_validator(self, response: Response) -> None:
@keyword
def assert_href_to_resource_is_valid(
- self, href: str, referenced_resource: dict[str, JSON]
+ self, href: str, referenced_resource: JSON
) -> None:
"""
Attempt to GET the resource referenced by the `href` and validate it's equal
- to the provided `referenced_resource` object / dictionary.
+ to the provided `referenced_resource`.
"""
_validation.assert_href_to_resource_is_valid(
href=href,
@@ -502,7 +657,7 @@ def validate_response(
self,
path: str,
response: Response,
- original_data: Mapping[str, JSON] = default_json_mapping,
+ original_data: JSON = default_json_mapping, # type: ignore[assignment]
) -> None:
"""
Validate the `response` by performing the following validations:
@@ -520,7 +675,7 @@ def validate_response(
response_validator=self.response_validator,
server_validation_warning_logged=self._server_validation_warning_logged,
disable_server_validation=self.disable_server_validation,
- invalid_property_default_response=self.invalid_property_default_response,
+ invalid_data_default_response=self.invalid_data_default_response,
response_validation=self.response_validation,
openapi_spec=self.openapi_spec,
original_data=original_data,
@@ -552,11 +707,6 @@ def origin(self) -> str:
def base_url(self) -> str:
return f"{self.origin}{self._base_path}"
- @cached_property
- def validation_spec(self) -> Spec:
- _, validation_spec, _ = self._load_specs_and_validator()
- return validation_spec
-
@property
def openapi_spec(self) -> OpenApiObject:
"""Return a deepcopy of the parsed openapi document."""
@@ -565,19 +715,65 @@ def openapi_spec(self) -> OpenApiObject:
@cached_property
def _openapi_spec(self) -> OpenApiObject:
- parser, _, _ = self._load_specs_and_validator()
- spec_model = OpenApiObject.model_validate(parser.specification)
- register_path_parameters(spec_model.paths)
+ specification, _ = self._load_specs_and_validator()
+ spec_model = OpenApiObject.model_validate(specification)
+ spec_model = self._perform_post_init_model_updates(spec_model=spec_model)
+ self._register_path_parameters(spec_model.paths)
+ return spec_model
+
+ def _register_path_parameters(self, paths_data: dict[str, PathItemObject]) -> None:
+ def _register_path_parameter(parameter_object: ParameterObject) -> None:
+ if parameter_object.in_ == "path":
+ _ = get_safe_name_for_oas_name(parameter_object.name)
+
+ for path_item in paths_data.values():
+ operations = path_item.operations
+ for operation in operations.values():
+ if parameters := operation.parameters:
+ for parameter in parameters:
+ _register_path_parameter(parameter_object=parameter)
+
+ def _perform_post_init_model_updates(
+ self, spec_model: OpenApiObject
+ ) -> OpenApiObject:
+ for (
+ path,
+ operation,
+ ), data_relations in self.relations_mapping_dict.items():
+ try:
+ operation_item = getattr(spec_model.paths[path], operation.lower())
+ operation_item.relations_mapping = data_relations
+ except KeyError:
+ logger.warn(
+ f"The RELATIONS_MAPPING contains a path that is not found in the OpenAPI spec: {path}"
+ )
+
+ for path, path_relation in self.path_mapping_dict.items():
+ try:
+ path_item = spec_model.paths[path]
+ path_item.relations_mapping = path_relation
+ except KeyError:
+ logger.warn(
+ f"The PATH_MAPPING contains a path that is not found in the OpenAPI spec: {path}"
+ )
+
+ for path, path_item in spec_model.paths.items():
+ mapper = self.get_id_property_name(path)
+ path_item.id_mapper = mapper
+ path_item.update_operation_parameters()
+ path_item.attach_relations_mappings()
+ path_item.replace_nullable_with_union()
+
return spec_model
@cached_property
def response_validator(
self,
- ) -> ResponseValidatorType:
- _, _, response_validator = self._load_specs_and_validator()
+ ) -> IResponseValidator:
+ _, response_validator = self._load_specs_and_validator()
return response_validator
- def _get_json_types_from_spec(self, spec: dict[str, JSON]) -> set[str]:
+ def _get_json_types_from_spec(self, spec: Mapping[str, JSON]) -> set[str]:
json_types: set[str] = set(self._get_json_types(spec))
return {json_type for json_type in json_types if json_type is not None}
@@ -601,9 +797,8 @@ def _get_json_types(self, item: object) -> Generator[str, None, None]:
def _load_specs_and_validator(
self,
) -> tuple[
- ResolvingParser,
- Spec,
- ResponseValidatorType,
+ Mapping[str, JSON],
+ IResponseValidator,
]:
def recursion_limit_handler(
limit: int, # pylint: disable=unused-argument
@@ -613,50 +808,38 @@ def recursion_limit_handler(
return self._recursion_default # pragma: no cover
try:
- # Since parsing of the OAS and creating the Spec can take a long time,
+ # Since parsing of the OAS and the specification can take a long time,
# they are cached. This is done by storing them in an imported module that
# will have a global scope due to how the Python import system works. This
# ensures that in a Suite of Suites where multiple Suites use the same
# `source`, that OAS is only parsed / loaded once.
- cached_parser = PARSER_CACHE.get(self._source, None)
- if cached_parser:
+ cached_spec = SPEC_CACHE.get(self._source, None)
+ if cached_spec:
return (
- cached_parser.parser,
- cached_parser.validation_spec,
- cached_parser.response_validator,
+ cached_spec.specification,
+ cached_spec.response_validator,
)
- parser = ResolvingParser(
- self._source,
- backend="openapi-spec-validator",
- recursion_limit=self._recursion_limit,
- recursion_limit_handler=recursion_limit_handler,
- )
+ specification = self._get_specification(recursion_limit_handler)
- if parser.specification is None: # pragma: no cover
- raise FatalError(
- "Source was loaded, but no specification was present after parsing."
- )
-
- validation_spec = Spec.from_dict(parser.specification) # pyright: ignore[reportArgumentType]
+ validation_spec = SchemaPath.from_dict(specification) # type: ignore[arg-type]
json_types_from_spec: set[str] = self._get_json_types_from_spec(
- parser.specification
+ specification
)
extra_deserializers = {
json_type: _json.loads for json_type in json_types_from_spec
}
config = Config(extra_media_type_deserializers=extra_deserializers) # type: ignore[arg-type]
openapi = OpenAPI(spec=validation_spec, config=config)
- response_validator: ResponseValidatorType = openapi.validate_response # type: ignore[assignment]
+ response_validator: IResponseValidator = openapi.validate_response
- PARSER_CACHE[self._source] = CachedParser(
- parser=parser,
- validation_spec=validation_spec,
+ SPEC_CACHE[self._source] = CachedSpec(
+ specification=specification,
response_validator=response_validator,
)
- return parser, validation_spec, response_validator
+ return specification, response_validator
except ResolutionError as exception: # pragma: no cover
raise FatalError(
@@ -667,6 +850,62 @@ def recursion_limit_handler(
f"ValidationError while trying to load openapi spec: {exception}"
) from exception
+ def _get_specification(
+ self, recursion_limit_handler: Callable[[int, str, JSON], JSON]
+ ) -> Mapping[str, JSON]:
+ if Path(self._source).is_file():
+ return self._load_specification(
+ filepath=self._source, recursion_limit_handler=recursion_limit_handler
+ )
+
+ try:
+ response = self.authorized_request(url=self._source, method="GET")
+ response.raise_for_status()
+ except Exception as exception: # pragma: no cover
+ raise FatalError(
+ f"Failed to download the OpenAPI spec using an authorized request."
+ f"\nThis download attempt was made since the provided `source` "
+ f"does not point to a file.\nPlease verify the source path is "
+ f"correct if you intent to reference a local file. "
+ f"\nMake sure the source url is correct and reachable if "
+ f"referencing a web resource."
+ f"\nThe exception was: {exception}"
+ )
+
+ _, _, filename = self._source.rpartition("/")
+ with tempfile.TemporaryDirectory() as tempdir:
+ filepath = Path(tempdir, filename)
+ with open(file=filepath, mode="w", encoding="UTF-8") as spec_file:
+ spec_file.write(response.text)
+
+ return self._load_specification(
+ filepath=filepath.as_posix(),
+ recursion_limit_handler=recursion_limit_handler,
+ )
+
+ def _load_specification(
+ self, filepath: str, recursion_limit_handler: Callable[[int, str, JSON], JSON]
+ ) -> Mapping[str, JSON]:
+ try:
+ parser = ResolvingParser(
+ filepath,
+ backend="openapi-spec-validator",
+ recursion_limit=self._recursion_limit,
+ recursion_limit_handler=recursion_limit_handler,
+ ) # type: ignore[no-untyped-call]
+ except Exception as exception: # pragma: no cover
+ raise FatalError(
+ f"Failed to parse the OpenAPI spec downloaded to {filepath}"
+ f"\nThe exception was: {exception}"
+ )
+
+ if parser.specification is None: # pragma: no cover
+ raise FatalError(
+ "Source was loaded, but no specification was present after parsing."
+ )
+
+ return parser.specification # type: ignore[no-any-return]
+
def read_paths(self) -> dict[str, PathItemObject]:
return self.openapi_spec.paths
diff --git a/src/OpenApiLibCore/protocols.py b/src/OpenApiLibCore/protocols.py
index 40958f7..ca9b877 100644
--- a/src/OpenApiLibCore/protocols.py
+++ b/src/OpenApiLibCore/protocols.py
@@ -1,38 +1,72 @@
"""A module holding Protcols."""
-from typing import Callable, Protocol, Type
+from __future__ import annotations
+
+import builtins
+from typing import Any, Callable, Protocol
from openapi_core.contrib.requests import (
RequestsOpenAPIRequest,
RequestsOpenAPIResponse,
)
+from pydantic import GetCoreSchemaHandler
+from pydantic_core import CoreSchema, core_schema
-from OpenApiLibCore.dto_base import Dto
+from OpenApiLibCore.models.resource_relations import (
+ PathPropertiesConstraint,
+ ResourceRelation,
+)
-class ResponseValidatorType(Protocol):
+class IResponseValidator(Protocol):
def __call__(
self, request: RequestsOpenAPIRequest, response: RequestsOpenAPIResponse
- ) -> None: ... # pragma: no cover
+ ) -> None: ...
-class GetDtoClassType(Protocol):
- def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
+class IGetIdPropertyName(Protocol):
+ def __init__(
+ self, mappings_module_name: str, default_id_property_name: str
+ ) -> None: ...
- def __call__(self, path: str, method: str) -> Type[Dto]: ... # pragma: no cover
+ def __call__(self, path: str) -> tuple[str, Callable[[str], str]]: ...
+ @property
+ def default_id_property_name(self) -> str: ...
-class GetIdPropertyNameType(Protocol):
- def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
+ @property
+ def id_mapping(
+ self,
+ ) -> dict[str, str | tuple[str, Callable[[str], str]]]: ...
- def __call__(
- self, path: str
- ) -> tuple[
- str, Callable[[str], str] | Callable[[int], int]
- ]: ... # pragma: no cover
+class IRelationsMapping(Protocol):
+ # NOTE: This Protocol is used as annotation in a number of the oas_models, which
+ # requires this method to prevent a PydanticSchemaGenerationError.
+ @classmethod
+ def __get_pydantic_core_schema__(
+ cls, source_type: Any, handler: GetCoreSchemaHandler
+ ) -> CoreSchema:
+ return core_schema.no_info_after_validator_function(cls, handler(str))
+
+ @staticmethod
+ def get_path_relations() -> list[PathPropertiesConstraint]: ...
+
+ @staticmethod
+ def get_parameter_relations() -> list[ResourceRelation]: ...
+
+ @classmethod
+ def get_parameter_relations_for_error_code(
+ cls, error_code: int
+ ) -> list[ResourceRelation]: ...
+
+ @staticmethod
+ def get_relations() -> list[ResourceRelation]: ...
+
+ @classmethod
+ def get_body_relations_for_error_code(
+ cls, error_code: int
+ ) -> list[ResourceRelation]: ...
-class GetPathDtoClassType(Protocol):
- def __init__(self, mappings_module_name: str) -> None: ... # pragma: no cover
- def __call__(self, path: str) -> Type[Dto]: ... # pragma: no cover
+RelationsMappingType = builtins.type[IRelationsMapping]
diff --git a/src/OpenApiLibCore/resource_relations.py b/src/OpenApiLibCore/resource_relations.py
deleted file mode 100644
index 600ff03..0000000
--- a/src/OpenApiLibCore/resource_relations.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""Module holding the functions related to relations between resources."""
-
-from requests import Response
-from robot.api import logger
-from robot.libraries.BuiltIn import BuiltIn
-
-import OpenApiLibCore.path_functions as _path_functions
-from OpenApiLibCore.dto_base import IdReference
-from OpenApiLibCore.models import OpenApiObject
-from OpenApiLibCore.request_data import RequestData
-
-run_keyword = BuiltIn().run_keyword
-
-
-def ensure_in_use(
- url: str,
- base_url: str,
- openapi_spec: OpenApiObject,
- resource_relation: IdReference,
-) -> None:
- resource_id = ""
-
- path = url.replace(base_url, "")
- path_parts = path.split("/")
- parameterized_path = _path_functions.get_parametrized_path(
- path=path, openapi_spec=openapi_spec
- )
- parameterized_path_parts = parameterized_path.split("/")
- for part, param_part in zip(
- reversed(path_parts), reversed(parameterized_path_parts)
- ):
- if param_part.endswith("}"):
- resource_id = part
- break
- if not resource_id:
- raise ValueError(f"The provided url ({url}) does not contain an id.")
- request_data: RequestData = run_keyword(
- "get_request_data", resource_relation.post_path, "post"
- )
- json_data = request_data.dto.as_dict()
- json_data[resource_relation.property_name] = resource_id
- post_url: str = run_keyword("get_valid_url", resource_relation.post_path)
- response: Response = run_keyword(
- "authorized_request",
- post_url,
- "post",
- request_data.params,
- request_data.headers,
- json_data,
- )
- if not response.ok:
- logger.debug(
- f"POST on {post_url} with json {json_data} failed: {response.json()}"
- )
- response.raise_for_status()
diff --git a/src/OpenApiLibCore/utils/__init__.py b/src/OpenApiLibCore/utils/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/OpenApiLibCore/utils/id_mapping.py b/src/OpenApiLibCore/utils/id_mapping.py
new file mode 100644
index 0000000..e2f12ab
--- /dev/null
+++ b/src/OpenApiLibCore/utils/id_mapping.py
@@ -0,0 +1,2 @@
+def dummy_transformer(valid_id: str) -> str:
+ return valid_id
diff --git a/src/OpenApiLibCore/utils/oas_cache.py b/src/OpenApiLibCore/utils/oas_cache.py
new file mode 100644
index 0000000..25bbd11
--- /dev/null
+++ b/src/OpenApiLibCore/utils/oas_cache.py
@@ -0,0 +1,16 @@
+"""Module holding the (global) spec cache."""
+
+from dataclasses import dataclass
+from typing import Mapping
+
+from OpenApiLibCore.annotations import JSON
+from OpenApiLibCore.protocols import IResponseValidator
+
+
+@dataclass
+class CachedSpec:
+ specification: Mapping[str, JSON]
+ response_validator: IResponseValidator
+
+
+SPEC_CACHE: dict[str, CachedSpec] = {}
diff --git a/src/OpenApiLibCore/parameter_utils.py b/src/OpenApiLibCore/utils/parameter_utils.py
similarity index 78%
rename from src/OpenApiLibCore/parameter_utils.py
rename to src/OpenApiLibCore/utils/parameter_utils.py
index 4ff258c..5b2d6fe 100644
--- a/src/OpenApiLibCore/parameter_utils.py
+++ b/src/OpenApiLibCore/utils/parameter_utils.py
@@ -5,8 +5,6 @@
from typing import Generator
-from OpenApiLibCore.models import ParameterObject, PathItemObject
-
PARAMETER_REGISTRY: dict[str, str] = {
"body": "body",
}
@@ -72,22 +70,9 @@ def _convert_string_to_python_identifier() -> Generator[str, None, None]:
yield f"_{ascii_code}_"
if _is_python_safe(string):
- return string
+ return string # pragma: no cover
converted_string = "".join(_convert_string_to_python_identifier())
- if not _is_python_safe(converted_string):
+ if not _is_python_safe(converted_string): # pragma: no cover
raise ValueError(f"Failed to convert '{string}' to Python identifier.")
return converted_string
-
-
-def register_path_parameters(paths_data: dict[str, PathItemObject]) -> None:
- def _register_path_parameter(parameter_object: ParameterObject) -> None:
- if parameter_object.in_ == "path":
- _ = get_safe_name_for_oas_name(parameter_object.name)
-
- for path_item in paths_data.values():
- operations = path_item.get_operations()
- for operation in operations.values():
- if parameters := operation.parameters:
- for parameter in parameters:
- _register_path_parameter(parameter_object=parameter)
diff --git a/src/OpenApiLibCore/value_utils.py b/src/OpenApiLibCore/value_utils.py
deleted file mode 100644
index ec133f8..0000000
--- a/src/OpenApiLibCore/value_utils.py
+++ /dev/null
@@ -1,216 +0,0 @@
-# mypy: disable-error-code=no-any-return
-"""Utility module with functions to handle OpenAPI value types and restrictions."""
-
-from copy import deepcopy
-from random import choice
-from typing import Any, Iterable, cast, overload
-
-from OpenApiLibCore.annotations import JSON
-from OpenApiLibCore.localized_faker import FAKE
-from OpenApiLibCore.models import ResolvedSchemaObjectTypes
-
-
-class Ignore:
- """Helper class to flag properties to be ignored in data generation."""
-
- def __str__(self) -> str:
- return "IGNORE"
-
-
-class UnSet:
- """Helper class to flag arguments that have not been set in a keyword call."""
-
- def __str__(self) -> str:
- return "UNSET"
-
-
-IGNORE = Ignore()
-
-UNSET = UnSet()
-
-
-def json_type_name_of_python_type(python_type: Any) -> str:
- """Return the JSON type name for supported Python types."""
- if python_type == str:
- return "string"
- if python_type == bool:
- return "boolean"
- if python_type == int:
- return "integer"
- if python_type == float:
- return "number"
- if python_type == list:
- return "array"
- if python_type == dict:
- return "object"
- if python_type == type(None):
- return "null"
- raise ValueError(f"No json type mapping for Python type {python_type} available.")
-
-
-def python_type_by_json_type_name(type_name: str) -> type:
- """Return the Python type based on the JSON type name."""
- if type_name == "string":
- return str
- if type_name == "boolean":
- return bool
- if type_name == "integer":
- return int
- if type_name == "number":
- return float
- if type_name == "array":
- return list
- if type_name == "object":
- return dict
- if type_name == "null":
- return type(None)
- raise ValueError(f"No Python type mapping for JSON type '{type_name}' available.")
-
-
-def get_invalid_value(
- value_schema: ResolvedSchemaObjectTypes,
- current_value: JSON,
- values_from_constraint: Iterable[JSON] = tuple(),
-) -> JSON | Ignore:
- """Return a random value that violates the provided value_schema."""
- invalid_values: list[JSON | Ignore] = []
- value_type = value_schema.type
-
- if not isinstance(current_value, python_type_by_json_type_name(value_type)):
- current_value = value_schema.get_valid_value()
-
- if values_from_constraint:
- try:
- return get_invalid_value_from_constraint(
- values_from_constraint=list(values_from_constraint),
- value_type=value_type,
- )
- except ValueError:
- pass
-
- # For schemas with a const or enum, add invalidated values from those
- try:
- invalid_value = value_schema.get_invalid_value_from_const_or_enum()
- invalid_values.append(invalid_value)
- except ValueError:
- pass
-
- # Violate min / max values or length if possible
- try:
- values_out_of_bounds = value_schema.get_values_out_of_bounds(
- current_value=current_value # type: ignore[arg-type]
- )
- invalid_values += values_out_of_bounds
- except ValueError:
- pass
-
- # No value constraints or min / max ranges to violate, so change the data type
- if value_type == "string":
- # Since int / float / bool can always be cast to sting, change
- # the string to a nested object.
- # An array gets exploded in query strings, "null" is then often invalid
- invalid_values.append([{"invalid": [None, False]}, "null", None, True])
- else:
- invalid_values.append(FAKE.uuid())
-
- return choice(invalid_values)
-
-
-def get_invalid_value_from_constraint(
- values_from_constraint: list[JSON | Ignore], value_type: str
-) -> JSON | Ignore:
- """
- Return a value of the same type as the values in the values_from_constraints that
- is not in the values_from_constraints, if possible. Otherwise returns None.
- """
- # if IGNORE is in the values_from_constraints, the parameter needs to be
- # ignored for an OK response so leaving the value at it's original value
- # should result in the specified error response
- if any(map(lambda x: isinstance(x, Ignore), values_from_constraint)):
- return IGNORE
- # if the value is forced True or False, return the opposite to invalidate
- if len(values_from_constraint) == 1 and value_type == "boolean":
- return not values_from_constraint[0]
- # for unsupported types or empty constraints lists raise a ValueError
- if (
- value_type not in ["string", "integer", "number", "array", "object"]
- or not values_from_constraint
- ):
- raise ValueError(
- f"Cannot get invalid value for {value_type} from {values_from_constraint}"
- )
-
- values_from_constraint = deepcopy(values_from_constraint)
- # for objects, keep the keys intact but update the values
- if value_type == "object":
- valid_object = cast(dict[str, JSON], values_from_constraint.pop())
- invalid_object: dict[str, JSON] = {}
- for key, value in valid_object.items():
- python_type_of_value = type(value)
- json_type_of_value = json_type_name_of_python_type(python_type_of_value)
- invalid_value = cast(
- JSON,
- get_invalid_value_from_constraint(
- values_from_constraint=[value],
- value_type=json_type_of_value,
- ),
- )
- invalid_object[key] = invalid_value
- return invalid_object
-
- # for arrays, update each value in the array to a value of the same type
- if value_type == "array":
- valid_array = cast(list[JSON], values_from_constraint.pop())
- invalid_array: list[JSON] = []
- for value in valid_array:
- python_type_of_value = type(value)
- json_type_of_value = json_type_name_of_python_type(python_type_of_value)
- invalid_value = cast(
- JSON,
- get_invalid_value_from_constraint(
- values_from_constraint=[value],
- value_type=json_type_of_value,
- ),
- )
- invalid_array.append(invalid_value)
- return invalid_array
-
- if value_type in ["integer", "number"]:
- int_or_number_list = cast(list[int | float], values_from_constraint)
- return get_invalid_int_or_number(values_from_constraint=int_or_number_list)
-
- str_or_bytes_list = cast(list[str] | list[bytes], values_from_constraint)
- invalid_value = get_invalid_str_or_bytes(values_from_constraint=str_or_bytes_list)
- if not invalid_value:
- raise ValueError("Value invalidation yielded an empty string.")
- return invalid_value
-
-
-def get_invalid_int_or_number(values_from_constraint: list[int | float]) -> int | float:
- invalid_values = 2 * values_from_constraint
- invalid_value = invalid_values.pop()
- for value in invalid_values:
- invalid_value = abs(invalid_value) + abs(value)
- if not invalid_value:
- invalid_value += 1
- return invalid_value
-
-
-@overload
-def get_invalid_str_or_bytes(
- values_from_constraint: list[str],
-) -> str: ... # pragma: no cover
-
-
-@overload
-def get_invalid_str_or_bytes(
- values_from_constraint: list[bytes],
-) -> bytes: ... # pragma: no cover
-
-
-def get_invalid_str_or_bytes(values_from_constraint: list[Any]) -> Any:
- invalid_values = 2 * values_from_constraint
- invalid_value = invalid_values.pop()
- for value in invalid_values:
- invalid_value = invalid_value + value
- return invalid_value
diff --git a/src/openapi_libgen/command_line.py b/src/openapi_libgen/command_line.py
index 38f8039..c675419 100644
--- a/src/openapi_libgen/command_line.py
+++ b/src/openapi_libgen/command_line.py
@@ -72,7 +72,7 @@ def main() -> None:
default_module_name,
)
- use_summary = getenv("USE_SUMMARY_AS_KEYWORD_NAME")
+ use_summary: str | bool | None = getenv("USE_SUMMARY_AS_KEYWORD_NAME")
if use_summary is None:
if args.use_summary_as_keyword_name is None:
use_summary = input(
@@ -80,7 +80,7 @@ def main() -> None:
)
use_summary = True if use_summary.lower().startswith("y") else False
- expand_body = getenv("EXPAND_BODY_ARGUMENTS")
+ expand_body: str | bool | None = getenv("EXPAND_BODY_ARGUMENTS")
if expand_body is None:
if args.expand_body_arguments is None:
expand_body = input(
@@ -93,6 +93,6 @@ def main() -> None:
output_folder=path,
library_name=safe_library_name,
module_name=safe_module_name,
- use_summary=is_truthy(use_summary),
- expand_body=is_truthy(expand_body),
+ use_summary=is_truthy(use_summary), # type: ignore[no-untyped-call]
+ expand_body=is_truthy(expand_body), # type: ignore[no-untyped-call]
)
diff --git a/src/openapi_libgen/generator.py b/src/openapi_libgen/generator.py
index 2114c2a..82e8434 100644
--- a/src/openapi_libgen/generator.py
+++ b/src/openapi_libgen/generator.py
@@ -7,7 +7,7 @@
from robot.utils import is_truthy
from openapi_libgen.spec_parser import get_keyword_data
-from OpenApiLibCore.models import OpenApiObject
+from OpenApiLibCore.models.oas_models import OpenApiObject
HERE = Path(__file__).parent.resolve()
@@ -25,7 +25,7 @@ def recursion_limit_handler(
backend="openapi-spec-validator",
recursion_limit=recursion_limit,
recursion_limit_handler=recursion_limit_handler,
- )
+ ) # type: ignore[no-untyped-call]
assert parser.specification is not None, (
"Source was loaded, but no specification was present after parsing."
)
@@ -81,11 +81,11 @@ def generate(
use_summary = getenv("USE_SUMMARY_AS_KEYWORD_NAME")
use_summary = use_summary if use_summary is not None else sys.argv[5]
- use_summary = is_truthy(use_summary)
+ use_summary = is_truthy(use_summary) # type: ignore[no-untyped-call]
expand_body = getenv("EXPAND_BODY_ARGUMENTS")
expand_body = expand_body if expand_body is not None else sys.argv[6]
- expand_body = is_truthy(expand_body)
+ expand_body = is_truthy(expand_body) # type: ignore[no-untyped-call]
spec = load_openapi_spec(source=source, recursion_limit=1, recursion_default={})
diff --git a/src/openapi_libgen/spec_parser.py b/src/openapi_libgen/spec_parser.py
index e8b8c7b..8b4ad8f 100644
--- a/src/openapi_libgen/spec_parser.py
+++ b/src/openapi_libgen/spec_parser.py
@@ -2,14 +2,14 @@
from typing import Generator
from openapi_libgen.parsing_utils import remove_unsafe_characters_from_string
-from OpenApiLibCore.models import (
+from OpenApiLibCore.models.oas_models import (
ObjectSchema,
OpenApiObject,
OperationObject,
PathItemObject,
SchemaObjectTypes,
)
-from OpenApiLibCore.parameter_utils import get_safe_name_for_oas_name
+from OpenApiLibCore.utils.parameter_utils import get_safe_name_for_oas_name
KEYWORD_TEMPLATE = r"""@keyword
{signature}
@@ -47,7 +47,7 @@ def get_path_items(
paths: dict[str, PathItemObject],
) -> Generator[OperationDetails, None, None]:
for path, path_item_object in paths.items():
- operations = path_item_object.get_operations()
+ operations = path_item_object.operations
for method, operation_object in operations.items():
operation_details = OperationDetails(
path=path,
diff --git a/src/openapi_libgen/templates/library.jinja b/src/openapi_libgen/templates/library.jinja
index 5dd486c..11a91f2 100644
--- a/src/openapi_libgen/templates/library.jinja
+++ b/src/openapi_libgen/templates/library.jinja
@@ -6,7 +6,8 @@ from robot.api.deco import keyword, library
from robot.libraries.BuiltIn import BuiltIn
from OpenApiLibCore import UNSET, OpenApiLibCore, RequestValues
-from OpenApiLibCore.path_functions import substitute_path_parameters
+from OpenApiLibCore.annotations import JSON
+from OpenApiLibCore.keyword_logic.path_functions import substitute_path_parameters
run_keyword = BuiltIn().run_keyword
@@ -20,7 +21,7 @@ class {{ library_name }}(OpenApiLibCore):
@staticmethod
def _perform_request(request_values: RequestValues) -> Response:
response: Response = run_keyword(
- "authorized_request",
+ "Authorized Request",
request_values.url,
request_values.method,
request_values.params,
diff --git a/src/openapitools_docs/docstrings.py b/src/openapitools_docs/docstrings.py
index 20e9bff..05102f7 100644
--- a/src/openapitools_docs/docstrings.py
+++ b/src/openapitools_docs/docstrings.py
@@ -170,7 +170,7 @@
mappings_path
See the Advanced Use tab for an in-depth explanation.
-
invalid_property_default_response
+
invalid_data_default_response
The default response code for requests with a JSON body that does not comply
with the schema.
Example: a value outside the specified range or a string value
@@ -498,10 +498,10 @@
@@ -547,13 +547,13 @@ def get_parameter_relations():
Here the classes needed to implement custom mappings are imported.
This section can just be copied without changes.
The ID_MAPPING "constant" definition / assignment.
-
The section defining the mapping Dtos. More on this later.
-
The DTO_MAPPING "constant" definition / assignment.
+
The section defining the RelationsMappings. More on this later.
+
The RELATIONS_MAPPING "constant" definition / assignment.
The PATH_MAPPING "constant" definition / assignment.
-
The ID_MAPPING, DTO_MAPPING and PATH_MAPPING
-When a custom mappings file is used, the OpenApiLibCore will attempt to import it and then import DTO_MAPPING, PATH_MAPPING and ID_MAPPING from it.
+
The ID_MAPPING, RELATIONS_MAPPING and PATH_MAPPING
+When a custom mappings file is used, the OpenApiLibCore will attempt to import it and then import RELATIONS_MAPPING, PATH_MAPPING and ID_MAPPING from it.
For this reason, the exact same name must be used in a custom mappings file (capitilization matters).
-The DTO_MAPPING is a dictionary with a tuple as its key and a mappings Dto as its value.
+
The RELATIONS_MAPPING
+The RELATIONS_MAPPING is a dictionary with a tuple as its key and a RelationsMapping as its value.
The tuple must be in the form ("path_from_the_paths_section", "method_supported_by_the_path").
The path_from_the_paths_section must be exactly as found in the openapi document.
The method_supported_by_the_path must be one of the methods supported by the path and must be in lowercase.
The PATH_MAPPING
-The PATH_MAPPING is a dictionary with a "path_from_the_paths_section" as its key and a mappings Dto as its value.
+The PATH_MAPPING is a dictionary with a "path_from_the_paths_section" as its key and a RelationsMapping as its value.
The path_from_the_paths_section must be exactly as found in the openapi document.
-
Dto mapping classes
+
RelationsMapping classes
As can be seen from the import section above, a number of classes are available to deal with relations between resources and / or constraints on properties.
Each of these classes is designed to handle a relation or constraint commonly seen in REST APIs.
@@ -611,7 +611,7 @@ def my_transformer(identifier_name: str) -> str:
This relation can be implemented as follows:
@@ -655,7 +655,7 @@ def get_relations():
To verify that the specified error_code indeed occurs when attempting to delete the Wagegroup, we can implement the following dependency:
@@ -687,7 +687,7 @@ def get_relations():
To verify that the specified error_code occurs when attempting to post an Employee with an employee_number that is already in use, we can implement the following dependency:
-Note how this example reuses the EmployeeDto to model the uniqueness constraint for all the operations (post, put and patch) that all relate to the same employee_number.
+Note how this example reuses the EmployeeMapping to model the uniqueness constraint for all the operations (post, put and patch) that all relate to the same employee_number.
@@ -721,7 +721,7 @@ def get_relations():
This type of constraint can be modeled as follows:
@@ -745,7 +745,7 @@ def get_relations():
To support additional restrictions like these, the PropertyValueConstraint supports two additional properties: error_value and invalid_value_error_code:
-Note: The PathPropertiesConstraint is only applicable to the get_path_relations in a Dto and only the PATH_MAPPING uses the get_path_relations.
+Note: The PathPropertiesConstraint is only applicable to the get_path_relations in a RelationsMapping and only the PATH_MAPPING uses the get_path_relations.
To be able to automatically perform endpoint validations, the OpenApiLibCore has to construct the url for the resource from the path as found in the openapi document.
@@ -824,7 +824,7 @@ def get_relations():
It should be clear that the OpenApiLibCore won't be able to acquire a valid month and date. The PathPropertiesConstraint can be used in this case:
@@ -853,7 +853,7 @@ def get_path_relations():
To prevent OpenApiLibCore from generating invalid combinations of path and query parameters in this type of endpoint, the IGNORE special value can be used to ensure the related query parameter is never send in a request.