robotframework-openapitools 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -123,6 +123,7 @@ import re
123
123
  import sys
124
124
  from copy import deepcopy
125
125
  from dataclasses import Field, dataclass, field, make_dataclass
126
+ from enum import Enum
126
127
  from functools import cached_property
127
128
  from itertools import zip_longest
128
129
  from logging import getLogger
@@ -147,11 +148,16 @@ from openapi_core.contrib.requests import (
147
148
  RequestsOpenAPIRequest,
148
149
  RequestsOpenAPIResponse,
149
150
  )
150
- from prance import ResolvingParser, ValidationError
151
+ from openapi_core.exceptions import OpenAPIError
152
+ from openapi_core.validation.exceptions import ValidationError
153
+ from openapi_core.validation.response.exceptions import ResponseValidationError
154
+ from openapi_core.validation.schemas.exceptions import InvalidSchemaValue
155
+ from prance import ResolvingParser
151
156
  from prance.util.url import ResolutionError
152
157
  from requests import Response, Session
153
158
  from requests.auth import AuthBase, HTTPBasicAuth
154
159
  from requests.cookies import RequestsCookieJar as CookieJar
160
+ from robot.api import Failure
155
161
  from robot.api.deco import keyword, library
156
162
  from robot.libraries.BuiltIn import BuiltIn
157
163
 
@@ -181,6 +187,15 @@ run_keyword = BuiltIn().run_keyword
181
187
  logger = getLogger(__name__)
182
188
 
183
189
 
190
+ class ValidationLevel(str, Enum):
191
+ """The available levels for the response_validation parameter."""
192
+
193
+ DISABLED = "DISABLED"
194
+ INFO = "INFO"
195
+ WARN = "WARN"
196
+ STRICT = "STRICT"
197
+
198
+
184
199
  def get_safe_key(key: str) -> str:
185
200
  """
186
201
  Helper function to convert a valid JSON property name to a string that can be used
@@ -643,7 +658,8 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
643
658
  else:
644
659
  for content_type in content_dict:
645
660
  if "json" in content_type:
646
- yield content_type
661
+ content_type_without_charset, _, _ = content_type.partition(";")
662
+ yield content_type_without_charset
647
663
 
648
664
  if isinstance(item, list):
649
665
  for list_item in item:
@@ -1624,3 +1640,457 @@ class OpenApiLibCore: # pylint: disable=too-many-instance-attributes
1624
1640
  )
1625
1641
  logger.debug(f"Response text: {response.text}")
1626
1642
  return response
1643
+
1644
+ @keyword
1645
+ def perform_validated_request(
1646
+ self,
1647
+ path: str,
1648
+ status_code: int,
1649
+ request_values: RequestValues,
1650
+ original_data: Optional[Dict[str, Any]] = None,
1651
+ ) -> None:
1652
+ """
1653
+ This keyword first calls the Authorized Request keyword, then the Validate
1654
+ Response keyword and finally validates, for `DELETE` operations, whether
1655
+ the target resource was indeed deleted (OK response) or not (error responses).
1656
+ """
1657
+ response = run_keyword(
1658
+ "authorized_request",
1659
+ request_values.url,
1660
+ request_values.method,
1661
+ request_values.params,
1662
+ request_values.headers,
1663
+ request_values.json_data,
1664
+ )
1665
+ if response.status_code != status_code:
1666
+ try:
1667
+ response_json = response.json()
1668
+ except Exception as _: # pylint: disable=broad-except
1669
+ logger.info(
1670
+ f"Failed to get json content from response. "
1671
+ f"Response text was: {response.text}"
1672
+ )
1673
+ response_json = {}
1674
+ if not response.ok:
1675
+ if description := response_json.get("detail"):
1676
+ pass
1677
+ else:
1678
+ description = response_json.get(
1679
+ "message", "response contains no message or detail."
1680
+ )
1681
+ logger.error(f"{response.reason}: {description}")
1682
+
1683
+ logger.debug(
1684
+ f"\nSend: {_json.dumps(request_values.json_data, indent=4, sort_keys=True)}"
1685
+ f"\nGot: {_json.dumps(response_json, indent=4, sort_keys=True)}"
1686
+ )
1687
+ raise AssertionError(
1688
+ f"Response status_code {response.status_code} was not {status_code}"
1689
+ )
1690
+
1691
+ run_keyword("validate_response", path, response, original_data)
1692
+
1693
+ if request_values.method == "DELETE":
1694
+ get_request_data = self.get_request_data(endpoint=path, method="GET")
1695
+ get_params = get_request_data.params
1696
+ get_headers = get_request_data.headers
1697
+ get_response = run_keyword(
1698
+ "authorized_request", request_values.url, "GET", get_params, get_headers
1699
+ )
1700
+ if response.ok:
1701
+ if get_response.ok:
1702
+ raise AssertionError(
1703
+ f"Resource still exists after deletion. Url was {request_values.url}"
1704
+ )
1705
+ # if the path supports GET, 404 is expected, if not 405 is expected
1706
+ if get_response.status_code not in [404, 405]:
1707
+ logger.warning(
1708
+ f"Unexpected response after deleting resource: Status_code "
1709
+ f"{get_response.status_code} was received after trying to get {request_values.url} "
1710
+ f"after sucessfully deleting it."
1711
+ )
1712
+ elif not get_response.ok:
1713
+ raise AssertionError(
1714
+ f"Resource could not be retrieved after failed deletion. "
1715
+ f"Url was {request_values.url}, status_code was {get_response.status_code}"
1716
+ )
1717
+
1718
+ @keyword
1719
+ def validate_response(
1720
+ self,
1721
+ path: str,
1722
+ response: Response,
1723
+ original_data: Optional[Dict[str, Any]] = None,
1724
+ ) -> None:
1725
+ """
1726
+ Validate the `response` by performing the following validations:
1727
+ - validate the `response` against the openapi schema for the `endpoint`
1728
+ - validate that the response does not contain extra properties
1729
+ - validate that a href, if present, refers to the correct resource
1730
+ - validate that the value for a property that is in the response is equal to
1731
+ the property value that was send
1732
+ - validate that no `original_data` is preserved when performing a PUT operation
1733
+ - validate that a PATCH operation only updates the provided properties
1734
+ """
1735
+ if response.status_code == 204:
1736
+ assert not response.content
1737
+ return None
1738
+
1739
+ try:
1740
+ self._validate_response_against_spec(response)
1741
+ except OpenAPIError as exception:
1742
+ raise Failure(f"Response did not pass schema validation: {exception}")
1743
+
1744
+ request_method = response.request.method
1745
+ if request_method is None:
1746
+ logger.warning(
1747
+ f"Could not validate response for path {path}; no method found "
1748
+ f"on the request property of the provided response."
1749
+ )
1750
+ return None
1751
+
1752
+ response_spec = self._get_response_spec(
1753
+ path=path,
1754
+ method=request_method,
1755
+ status_code=response.status_code,
1756
+ )
1757
+
1758
+ content_type_from_response = response.headers.get("Content-Type", "unknown")
1759
+ mime_type_from_response, _, _ = content_type_from_response.partition(";")
1760
+
1761
+ if not response_spec.get("content"):
1762
+ logger.warning(
1763
+ "The response cannot be validated: 'content' not specified in the OAS."
1764
+ )
1765
+ return None
1766
+
1767
+ # multiple content types can be specified in the OAS
1768
+ content_types = list(response_spec["content"].keys())
1769
+ supported_types = [
1770
+ ct for ct in content_types if ct.partition(";")[0].endswith("json")
1771
+ ]
1772
+ if not supported_types:
1773
+ raise NotImplementedError(
1774
+ f"The content_types '{content_types}' are not supported. "
1775
+ f"Only json types are currently supported."
1776
+ )
1777
+ content_type = supported_types[0]
1778
+ mime_type = content_type.partition(";")[0]
1779
+
1780
+ if mime_type != mime_type_from_response:
1781
+ raise ValueError(
1782
+ f"Content-Type '{content_type_from_response}' of the response "
1783
+ f"does not match '{mime_type}' as specified in the OpenAPI document."
1784
+ )
1785
+
1786
+ json_response = response.json()
1787
+ response_schema = resolve_schema(
1788
+ response_spec["content"][content_type]["schema"]
1789
+ )
1790
+
1791
+ response_type = response_schema.get("type", "undefined")
1792
+ if response_type not in ["object", "array"]:
1793
+ self._validate_value_type(value=json_response, expected_type=response_type)
1794
+ return None
1795
+
1796
+ if list_item_schema := response_schema.get("items"):
1797
+ if not isinstance(json_response, list):
1798
+ raise AssertionError(
1799
+ f"Response schema violation: the schema specifies an array as "
1800
+ f"response type but the response was of type {type(json_response)}."
1801
+ )
1802
+ type_of_list_items = list_item_schema.get("type")
1803
+ if type_of_list_items == "object":
1804
+ for resource in json_response:
1805
+ run_keyword(
1806
+ "validate_resource_properties", resource, list_item_schema
1807
+ )
1808
+ else:
1809
+ for item in json_response:
1810
+ self._validate_value_type(
1811
+ value=item, expected_type=type_of_list_items
1812
+ )
1813
+ # no further validation; value validation of individual resources should
1814
+ # be performed on the endpoints for the specific resource
1815
+ return None
1816
+
1817
+ run_keyword("validate_resource_properties", json_response, response_schema)
1818
+ # ensure the href is valid if present in the response
1819
+ if href := json_response.get("href"):
1820
+ self._assert_href_is_valid(href, json_response)
1821
+ # every property that was sucessfully send and that is in the response
1822
+ # schema must have the value that was send
1823
+ if response.ok and response.request.method in ["POST", "PUT", "PATCH"]:
1824
+ run_keyword("validate_send_response", response, original_data)
1825
+ return None
1826
+
1827
+ def _assert_href_is_valid(self, href: str, json_response: Dict[str, Any]) -> None:
1828
+ url = f"{self.origin}{href}"
1829
+ path = url.replace(self.base_url, "")
1830
+ request_data = self.get_request_data(endpoint=path, method="GET")
1831
+ params = request_data.params
1832
+ headers = request_data.headers
1833
+ get_response = run_keyword("authorized_request", url, "GET", params, headers)
1834
+ assert (
1835
+ get_response.json() == json_response
1836
+ ), f"{get_response.json()} not equal to original {json_response}"
1837
+
1838
+ def _validate_response_against_spec(self, response: Response) -> None:
1839
+ try:
1840
+ self.validate_response_vs_spec(
1841
+ request=RequestsOpenAPIRequest(response.request),
1842
+ response=RequestsOpenAPIResponse(response),
1843
+ )
1844
+ except ResponseValidationError as exception:
1845
+ errors: List[InvalidSchemaValue] = exception.__cause__
1846
+ validation_errors: Optional[List[ValidationError]] = getattr(
1847
+ errors, "schema_errors", None
1848
+ )
1849
+ if validation_errors:
1850
+ error_message = "\n".join(
1851
+ [
1852
+ f"{list(error.schema_path)}: {error.message}"
1853
+ for error in validation_errors
1854
+ ]
1855
+ )
1856
+ else:
1857
+ error_message = str(exception)
1858
+
1859
+ if response.status_code == self.invalid_property_default_response:
1860
+ logger.debug(error_message)
1861
+ return
1862
+ if self.response_validation == ValidationLevel.STRICT:
1863
+ logger.error(error_message)
1864
+ raise exception
1865
+ if self.response_validation == ValidationLevel.WARN:
1866
+ logger.warning(error_message)
1867
+ elif self.response_validation == ValidationLevel.INFO:
1868
+ logger.info(error_message)
1869
+
1870
+ @keyword
1871
+ def validate_resource_properties(
1872
+ self, resource: Dict[str, Any], schema: Dict[str, Any]
1873
+ ) -> None:
1874
+ """
1875
+ Validate that the `resource` does not contain any properties that are not
1876
+ defined in the `schema_properties`.
1877
+ """
1878
+ schema_properties = schema.get("properties", {})
1879
+ property_names_from_schema = set(schema_properties.keys())
1880
+ property_names_in_resource = set(resource.keys())
1881
+
1882
+ if property_names_from_schema != property_names_in_resource:
1883
+ # The additionalProperties property determines whether properties with
1884
+ # unspecified names are allowed. This property can be boolean or an object
1885
+ # (dict) that specifies the type of any additional properties.
1886
+ additional_properties = schema.get("additionalProperties", True)
1887
+ if isinstance(additional_properties, bool):
1888
+ allow_additional_properties = additional_properties
1889
+ allowed_additional_properties_type = None
1890
+ else:
1891
+ allow_additional_properties = True
1892
+ allowed_additional_properties_type = additional_properties["type"]
1893
+
1894
+ extra_property_names = property_names_in_resource.difference(
1895
+ property_names_from_schema
1896
+ )
1897
+ if allow_additional_properties:
1898
+ # If a type is defined for extra properties, validate them
1899
+ if allowed_additional_properties_type:
1900
+ extra_properties = {
1901
+ key: value
1902
+ for key, value in resource.items()
1903
+ if key in extra_property_names
1904
+ }
1905
+ self._validate_type_of_extra_properties(
1906
+ extra_properties=extra_properties,
1907
+ expected_type=allowed_additional_properties_type,
1908
+ )
1909
+ # If allowed, validation should not fail on extra properties
1910
+ extra_property_names = set()
1911
+
1912
+ required_properties = set(schema.get("required", []))
1913
+ missing_properties = required_properties.difference(
1914
+ property_names_in_resource
1915
+ )
1916
+
1917
+ if extra_property_names or missing_properties:
1918
+ extra = (
1919
+ f"\n\tExtra properties in response: {extra_property_names}"
1920
+ if extra_property_names
1921
+ else ""
1922
+ )
1923
+ missing = (
1924
+ f"\n\tRequired properties missing in response: {missing_properties}"
1925
+ if missing_properties
1926
+ else ""
1927
+ )
1928
+ raise AssertionError(
1929
+ f"Response schema violation: the response contains properties that are "
1930
+ f"not specified in the schema or does not contain properties that are "
1931
+ f"required according to the schema."
1932
+ f"\n\tReceived in the response: {property_names_in_resource}"
1933
+ f"\n\tDefined in the schema: {property_names_from_schema}"
1934
+ f"{extra}{missing}"
1935
+ )
1936
+
1937
+ @staticmethod
1938
+ def _validate_value_type(value: Any, expected_type: str) -> None:
1939
+ type_mapping = {
1940
+ "string": str,
1941
+ "number": float,
1942
+ "integer": int,
1943
+ "boolean": bool,
1944
+ "array": list,
1945
+ "object": dict,
1946
+ }
1947
+ python_type = type_mapping.get(expected_type, None)
1948
+ if python_type is None:
1949
+ raise AssertionError(
1950
+ f"Validation of type '{expected_type}' is not supported."
1951
+ )
1952
+ if not isinstance(value, python_type):
1953
+ raise AssertionError(f"{value} is not of type {expected_type}")
1954
+
1955
+ @staticmethod
1956
+ def _validate_type_of_extra_properties(
1957
+ extra_properties: Dict[str, Any], expected_type: str
1958
+ ) -> None:
1959
+ type_mapping = {
1960
+ "string": str,
1961
+ "number": float,
1962
+ "integer": int,
1963
+ "boolean": bool,
1964
+ "array": list,
1965
+ "object": dict,
1966
+ }
1967
+
1968
+ python_type = type_mapping.get(expected_type, None)
1969
+ if python_type is None:
1970
+ logger.warning(
1971
+ f"Additonal properties were not validated: "
1972
+ f"type '{expected_type}' is not supported."
1973
+ )
1974
+ return
1975
+
1976
+ invalid_extra_properties = {
1977
+ key: value
1978
+ for key, value in extra_properties.items()
1979
+ if not isinstance(value, python_type)
1980
+ }
1981
+ if invalid_extra_properties:
1982
+ raise AssertionError(
1983
+ f"Response contains invalid additionalProperties: "
1984
+ f"{invalid_extra_properties} are not of type {expected_type}."
1985
+ )
1986
+
1987
+ @staticmethod
1988
+ @keyword
1989
+ def validate_send_response(
1990
+ response: Response, original_data: Optional[Dict[str, Any]] = None
1991
+ ) -> None:
1992
+ """
1993
+ Validate that each property that was send that is in the response has the value
1994
+ that was send.
1995
+ In case a PATCH request, validate that only the properties that were patched
1996
+ have changed and that other properties are still at their pre-patch values.
1997
+ """
1998
+
1999
+ def validate_list_response(
2000
+ send_list: List[Any], received_list: List[Any]
2001
+ ) -> None:
2002
+ for item in send_list:
2003
+ if item not in received_list:
2004
+ raise AssertionError(
2005
+ f"Received value '{received_list}' does "
2006
+ f"not contain '{item}' in the {response.request.method} request."
2007
+ f"\nSend: {_json.dumps(send_json, indent=4, sort_keys=True)}"
2008
+ f"\nGot: {_json.dumps(response_data, indent=4, sort_keys=True)}"
2009
+ )
2010
+
2011
+ def validate_dict_response(
2012
+ send_dict: Dict[str, Any], received_dict: Dict[str, Any]
2013
+ ) -> None:
2014
+ for send_property_name, send_property_value in send_dict.items():
2015
+ # sometimes, a property in the request is not in the response, e.g. a password
2016
+ if send_property_name not in received_dict.keys():
2017
+ continue
2018
+ if send_property_value is not None:
2019
+ # if a None value is send, the target property should be cleared or
2020
+ # reverted to the default value (which cannot be specified in the
2021
+ # openapi document)
2022
+ received_value = received_dict[send_property_name]
2023
+ # In case of lists / arrays, the send values are often appended to
2024
+ # existing data
2025
+ if isinstance(received_value, list):
2026
+ validate_list_response(
2027
+ send_list=send_property_value, received_list=received_value
2028
+ )
2029
+ continue
2030
+
2031
+ # when dealing with objects, we'll need to iterate the properties
2032
+ if isinstance(received_value, dict):
2033
+ validate_dict_response(
2034
+ send_dict=send_property_value, received_dict=received_value
2035
+ )
2036
+ continue
2037
+
2038
+ assert received_value == send_property_value, (
2039
+ f"Received value for {send_property_name} '{received_value}' does not "
2040
+ f"match '{send_property_value}' in the {response.request.method} request."
2041
+ f"\nSend: {_json.dumps(send_json, indent=4, sort_keys=True)}"
2042
+ f"\nGot: {_json.dumps(response_data, indent=4, sort_keys=True)}"
2043
+ )
2044
+
2045
+ if response.request.body is None:
2046
+ logger.warning(
2047
+ "Could not validate send response; the body of the request property "
2048
+ "on the provided response was None."
2049
+ )
2050
+ return None
2051
+ if isinstance(response.request.body, bytes):
2052
+ send_json = _json.loads(response.request.body.decode("UTF-8"))
2053
+ else:
2054
+ send_json = _json.loads(response.request.body)
2055
+
2056
+ response_data = response.json()
2057
+ # POST on /resource_type/{id}/array_item/ will return the updated {id} resource
2058
+ # instead of a newly created resource. In this case, the send_json must be
2059
+ # in the array of the 'array_item' property on {id}
2060
+ send_path: str = response.request.path_url
2061
+ response_path = response_data.get("href", None)
2062
+ if response_path and send_path not in response_path:
2063
+ property_to_check = send_path.replace(response_path, "")[1:]
2064
+ if response_data.get(property_to_check) and isinstance(
2065
+ response_data[property_to_check], list
2066
+ ):
2067
+ item_list: List[Dict[str, Any]] = response_data[property_to_check]
2068
+ # Use the (mandatory) id to get the POSTed resource from the list
2069
+ [response_data] = [
2070
+ item for item in item_list if item["id"] == send_json["id"]
2071
+ ]
2072
+
2073
+ # incoming arguments are dictionaries, so they can be validated as such
2074
+ validate_dict_response(send_dict=send_json, received_dict=response_data)
2075
+
2076
+ # In case of PATCH requests, ensure that only send properties have changed
2077
+ if original_data:
2078
+ for send_property_name, send_value in original_data.items():
2079
+ if send_property_name not in send_json.keys():
2080
+ assert send_value == response_data[send_property_name], (
2081
+ f"Received value for {send_property_name} '{response_data[send_property_name]}' does not "
2082
+ f"match '{send_value}' in the pre-patch data"
2083
+ f"\nPre-patch: {_json.dumps(original_data, indent=4, sort_keys=True)}"
2084
+ f"\nGot: {_json.dumps(response_data, indent=4, sort_keys=True)}"
2085
+ )
2086
+ return None
2087
+
2088
+ def _get_response_spec(
2089
+ self, path: str, method: str, status_code: int
2090
+ ) -> Dict[str, Any]:
2091
+ method = method.lower()
2092
+ status = str(status_code)
2093
+ spec: Dict[str, Any] = {**self.openapi_spec}["paths"][path][method][
2094
+ "responses"
2095
+ ][status]
2096
+ return spec
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: robotframework-openapitools
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: A set of Robot Framework libraries to test APIs for which the OAS is available.
5
5
  Home-page: https://github.com/MarketSquare/robotframework-openapitools
6
6
  License: Apache-2.0
@@ -1,15 +1,15 @@
1
1
  OpenApiDriver/__init__.py,sha256=34h5RkB8nBNRKPId4r_B_xzcgXJYD3m2QYwIaPfpv80,1331
2
- OpenApiDriver/openapi_executors.py,sha256=XEuHUD-3mBNb8WNK8gmJoYkz2HAjzS-K9oQ0FTmgOoc,32851
2
+ OpenApiDriver/openapi_executors.py,sha256=7zjvMzkKY-IknTWZABZZoMNguMqXVn3gNiiljDQ9VyQ,12260
3
3
  OpenApiDriver/openapi_reader.py,sha256=4kSM-hFd54ws-jq88inbienkaoC5sSDwfRuxLrHP7aA,4652
4
- OpenApiDriver/openapidriver.libspec,sha256=3yU3o0AhCFulFWmmFwW-w0LY7GQaYSaU8er64Dr8cCA,28323
4
+ OpenApiDriver/openapidriver.libspec,sha256=JuR4-Feq6xy3WQZn0xe8U9jA0rVnnvfvLN-3X6YwDjY,28322
5
5
  OpenApiDriver/openapidriver.py,sha256=QRFcF1q9x88rqsD7XzBN25wPYqTbZldK4rSvpEdhQHo,15214
6
6
  OpenApiDriver/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- OpenApiLibCore/__init__.py,sha256=TOeGg8q0V8BNvdP9tEPE2MRQm7XyS1VzC5AP8y_qOPg,1591
8
- OpenApiLibCore/dto_base.py,sha256=L1jKJLOthqL7jjdfZ6HDmM3t967UiH7RWkvsAu6NURg,11794
7
+ OpenApiLibCore/__init__.py,sha256=CGZRj3Vh4TZ76LOLWJOrBFr27QYWVXsO1imLqH-aP9E,1604
8
+ OpenApiLibCore/dto_base.py,sha256=qAPSLnrs1n_sbfe1z-3EPV31Q7dMe0Nno2XRFmrYl9A,11792
9
9
  OpenApiLibCore/dto_utils.py,sha256=maYX9QqPaJtiEo8vIFQLsT2FOT-LUGVnNcVaca4zCDk,2903
10
10
  OpenApiLibCore/oas_cache.py,sha256=Qg_Is5pAJZjZu5VmwEEarQs8teKrh3Y2psCcpDLU5Y4,379
11
- OpenApiLibCore/openapi_libcore.libspec,sha256=9Kn1CGzUoIL1uzb5cpjQrJS7ZKxjHAvRqADzCzdGlKQ,33102
12
- OpenApiLibCore/openapi_libcore.py,sha256=GHuhcpMiDNGCy-0pmTB5WRbUDzK8wpL5O07lZo0kPVQ,65826
11
+ OpenApiLibCore/openapi_libcore.libspec,sha256=qgCF2aJGNWUgSX6-xjHQ2-DvknFGcA-1CnvU3VHCRzA,39801
12
+ OpenApiLibCore/openapi_libcore.py,sha256=BVBaeqv_ZgPw5332BmNeTjONdpL73dXPcf8w1bWtvBA,86685
13
13
  OpenApiLibCore/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  OpenApiLibCore/value_utils.py,sha256=wO5ssYTuk4YWWMJb0kl5n20X2r0NG55FXvgLqQd8qvw,18145
15
15
  roboswag/__init__.py,sha256=-8ql4wuY_ftOZRdCxSmBCMW3WpDk7Ir__st9xcb9qCI,223
@@ -35,7 +35,7 @@ roboswag/validate/__init__.py,sha256=stpgQmvZvqlqPBjZ3Vxhd3wbX_Nb85jyIbj44_EhK_w
35
35
  roboswag/validate/core.py,sha256=CfUEhkXPFAzIppRSiGyh62j4BYW4vkjIXWEzRcJFD6o,84
36
36
  roboswag/validate/schema.py,sha256=jyD44GcYU_JQLw5hb1wK-DwxOsbJ-FstoNHwIVVMqoo,711
37
37
  roboswag/validate/text_response.py,sha256=P7WEC6ot1OG3YDEXRtmOwIFwki8jgq8fMb-L77X4vIo,527
38
- robotframework_openapitools-0.2.1.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
39
- robotframework_openapitools-0.2.1.dist-info/METADATA,sha256=tT_w_nW-DgGsHRjhoWUQxRg92sQTdjTG7gzdPf19ndE,1610
40
- robotframework_openapitools-0.2.1.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
41
- robotframework_openapitools-0.2.1.dist-info/RECORD,,
38
+ robotframework_openapitools-0.2.2.dist-info/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
39
+ robotframework_openapitools-0.2.2.dist-info/METADATA,sha256=9b-sj3qOnu_xXjxDj5Ad-9Vp5RIIBvd-fav5IVCqk6I,1610
40
+ robotframework_openapitools-0.2.2.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
41
+ robotframework_openapitools-0.2.2.dist-info/RECORD,,