From 720368c7e8414de2b02f5b2eed09164f3fbd61a1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 May 2021 16:09:28 -0600 Subject: [PATCH 01/32] chore(deps): update dependency jinja2 to v3 (#424) Co-authored-by: Renovate Bot --- poetry.lock | 110 +++++++++++++++++++++---------------------------- pyproject.toml | 2 +- 2 files changed, 47 insertions(+), 65 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7db8957b7..0dba5fd26 100644 --- a/poetry.lock +++ b/poetry.lock @@ -259,25 +259,25 @@ colors = ["colorama (>=0.4.3,<0.5.0)"] [[package]] name = "jinja2" -version = "2.11.3" +version = "3.0.0" description = "A very fast and expressive template engine." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.6" [package.dependencies] -MarkupSafe = ">=0.23" +MarkupSafe = ">=2.0.0rc2" [package.extras] -i18n = ["Babel (>=0.8)"] +i18n = ["Babel (>=2.7)"] [[package]] name = "markupsafe" -version = "1.1.1" +version = "2.0.0" description = "Safely add untrusted strings to HTML/XML markup." category = "main" optional = false -python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" +python-versions = ">=3.6" [[package]] name = "mccabe" @@ -521,7 +521,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "rfc3986" -version = "1.4.0" +version = "1.5.0" description = "Validating URI References per RFC 3986" category = "main" optional = false @@ -670,7 +670,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake [metadata] lock-version = "1.1" python-versions = "^3.6.2" -content-hash = "b125cc4bc5456b335d0412e87dc21175738b4138a6eb9f4689b79d07171b8273" +content-hash = "41d160b8f136bcba70b7be42ffc5b11f9011671f49c88f51e75a537aa15adec1" [metadata.files] appdirs = [ @@ -806,62 +806,44 @@ isort = [ {file = "isort-5.8.0.tar.gz", hash = "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6"}, ] jinja2 = [ - {file = "Jinja2-2.11.3-py2.py3-none-any.whl", hash = "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419"}, - {file = "Jinja2-2.11.3.tar.gz", hash = "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6"}, + {file = "Jinja2-3.0.0-py3-none-any.whl", hash = "sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6"}, + {file = "Jinja2-3.0.0.tar.gz", hash = "sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"}, ] markupsafe = [ - {file = "MarkupSafe-1.1.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win32.whl", hash = "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b"}, - {file = "MarkupSafe-1.1.1-cp27-cp27m-win_amd64.whl", hash = "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f"}, - {file = "MarkupSafe-1.1.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-macosx_10_6_intel.whl", hash = "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win32.whl", hash = "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21"}, - {file = "MarkupSafe-1.1.1-cp34-cp34m-win_amd64.whl", hash = "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win32.whl", hash = "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1"}, - {file = "MarkupSafe-1.1.1-cp35-cp35m-win_amd64.whl", hash = "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win32.whl", hash = "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66"}, - {file = "MarkupSafe-1.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win32.whl", hash = "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2"}, - {file = "MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win32.whl", hash = "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b"}, - {file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win32.whl", hash = "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39"}, - {file = "MarkupSafe-1.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8"}, - {file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-win32.whl", hash = "sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715"}, + {file = "MarkupSafe-2.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-win32.whl", hash = "sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66"}, + {file = "MarkupSafe-2.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-win32.whl", hash = "sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d"}, + {file = "MarkupSafe-2.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-win32.whl", hash = "sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05"}, + {file = "MarkupSafe-2.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2"}, + {file = "MarkupSafe-2.0.0.tar.gz", hash = "sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, @@ -1048,8 +1030,8 @@ requests = [ {file = "requests-2.24.0.tar.gz", hash = "sha256:b3559a131db72c33ee969480840fff4bb6dd111de7dd27c8ee1f820f4f00231b"}, ] rfc3986 = [ - {file = "rfc3986-1.4.0-py2.py3-none-any.whl", hash = "sha256:af9147e9aceda37c91a05f4deb128d4b4b49d6b199775fd2d2927768abdc8f50"}, - {file = "rfc3986-1.4.0.tar.gz", hash = "sha256:112398da31a3344dc25dbf477d8df6cb34f9278a94fee2625d89e4514be8bb9d"}, + {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, + {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, ] safety = [ {file = "safety-1.10.3-py2.py3-none-any.whl", hash = "sha256:5f802ad5df5614f9622d8d71fedec2757099705c2356f862847c58c6dfe13e84"}, diff --git a/pyproject.toml b/pyproject.toml index d39350c1a..6a9052706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ include = ["CHANGELOG.md", "openapi_python_client/py.typed"] [tool.poetry.dependencies] python = "^3.6.2" -jinja2 = "^2.11.1" +jinja2 = "^3.0.0" typer = "^0.3" colorama = {version = "^0.4.3", markers = "sys_platform == 'win32'"} shellingham = "^1.3.2" From 64f67fe4d163e08fc516d869f1ac7ac7b55ef76e Mon Sep 17 00:00:00 2001 From: Christopher Keele Date: Tue, 11 May 2021 20:30:56 -0700 Subject: [PATCH 02/32] Soften black dependency. (#416) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6a9052706..6fb55a1a9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ jinja2 = "^3.0.0" typer = "^0.3" colorama = {version = "^0.4.3", markers = "sys_platform == 'win32'"} shellingham = "^1.3.2" -black = "^21.4b0" +black = "*" isort = "^5.0.5" pyyaml = "^5.3.1" importlib_metadata = {version = "^2.0.0", python = "<3.8"} From e708750b4e2b3706fb7e3cfabc704e6579a3b0f9 Mon Sep 17 00:00:00 2001 From: Forest Tong Date: Wed, 12 May 2021 10:38:50 -0400 Subject: [PATCH 03/32] fix: Deserialization of optional nullable properties when no value is returned from the API [#420 & #381]. Thanks @forest-benchling! * Handle optional nullable deserialization * Fix test --- .../my_test_api_client/models/a_model.py | 72 +++++++++++-------- .../models/model_with_any_json_properties.py | 2 - ...el_with_primitive_additional_properties.py | 6 +- .../models/model_with_property_ref.py | 6 +- .../models/model_with_union_property.py | 14 ++-- .../model_with_union_property_inlined.py | 14 ++-- .../property_macros.py.jinja | 22 +++--- .../union_property.py.jinja | 2 - .../test_date_property/optional_nullable.py | 8 ++- .../test_date_property/required_nullable.py | 6 +- 10 files changed, 89 insertions(+), 63 deletions(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py index 38ce8d114..0e2c06d83 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model.py @@ -186,7 +186,6 @@ def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.datetime]: try: - a_camel_date_time_type_0: datetime.datetime if not isinstance(data, str): raise TypeError() a_camel_date_time_type_0 = isoparse(data) @@ -196,7 +195,6 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date pass if not isinstance(data, str): raise TypeError() - a_camel_date_time_type_1: datetime.date a_camel_date_time_type_1 = isoparse(data).date() return a_camel_date_time_type_1 @@ -209,7 +207,6 @@ def _parse_a_camel_date_time(data: object) -> Union[datetime.date, datetime.date def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionProperty]: try: - one_of_models_type_0: FreeFormModel if not isinstance(data, dict): raise TypeError() one_of_models_type_0 = FreeFormModel.from_dict(data) @@ -219,7 +216,6 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro pass if not isinstance(data, dict): raise TypeError() - one_of_models_type_1: ModelWithUnionProperty one_of_models_type_1 = ModelWithUnionProperty.from_dict(data) return one_of_models_type_1 @@ -228,9 +224,11 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro model = ModelWithUnionProperty.from_dict(d.pop("model")) - an_optional_allof_enum: Union[Unset, AnAllOfEnum] = UNSET _an_optional_allof_enum = d.pop("an_optional_allof_enum", UNSET) - if not isinstance(_an_optional_allof_enum, Unset): + an_optional_allof_enum: Union[Unset, AnAllOfEnum] + if isinstance(_an_optional_allof_enum, Unset): + an_optional_allof_enum = UNSET + else: an_optional_allof_enum = AnAllOfEnum(_an_optional_allof_enum) nested_list_of_enums = [] @@ -245,14 +243,18 @@ def _parse_one_of_models(data: object) -> Union[FreeFormModel, ModelWithUnionPro nested_list_of_enums.append(nested_list_of_enums_item) - a_nullable_date = None _a_nullable_date = d.pop("a_nullable_date") - if _a_nullable_date is not None: + a_nullable_date: Optional[datetime.date] + if _a_nullable_date is None: + a_nullable_date = None + else: a_nullable_date = isoparse(_a_nullable_date).date() - a_not_required_date: Union[Unset, datetime.date] = UNSET _a_not_required_date = d.pop("a_not_required_date", UNSET) - if not isinstance(_a_not_required_date, Unset): + a_not_required_date: Union[Unset, datetime.date] + if isinstance(_a_not_required_date, Unset): + a_not_required_date = UNSET + else: a_not_required_date = isoparse(_a_not_required_date).date() attr_1_leading_digit = d.pop("1_leading_digit", UNSET) @@ -267,7 +269,6 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit if data is None: return data try: - nullable_one_of_models_type_0: FreeFormModel if not isinstance(data, dict): raise TypeError() nullable_one_of_models_type_0 = FreeFormModel.from_dict(data) @@ -277,7 +278,6 @@ def _parse_nullable_one_of_models(data: object) -> Union[FreeFormModel, ModelWit pass if not isinstance(data, dict): raise TypeError() - nullable_one_of_models_type_1: ModelWithUnionProperty nullable_one_of_models_type_1 = ModelWithUnionProperty.from_dict(data) return nullable_one_of_models_type_1 @@ -288,12 +288,13 @@ def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, Mode if isinstance(data, Unset): return data try: - not_required_one_of_models_type_0: Union[Unset, FreeFormModel] if not isinstance(data, dict): raise TypeError() - not_required_one_of_models_type_0 = UNSET _not_required_one_of_models_type_0 = data - if not isinstance(_not_required_one_of_models_type_0, Unset): + not_required_one_of_models_type_0: Union[Unset, FreeFormModel] + if isinstance(_not_required_one_of_models_type_0, Unset): + not_required_one_of_models_type_0 = UNSET + else: not_required_one_of_models_type_0 = FreeFormModel.from_dict(_not_required_one_of_models_type_0) return not_required_one_of_models_type_0 @@ -301,10 +302,11 @@ def _parse_not_required_one_of_models(data: object) -> Union[FreeFormModel, Mode pass if not isinstance(data, dict): raise TypeError() - not_required_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] - not_required_one_of_models_type_1 = UNSET _not_required_one_of_models_type_1 = data - if not isinstance(_not_required_one_of_models_type_1, Unset): + not_required_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] + if isinstance(_not_required_one_of_models_type_1, Unset): + not_required_one_of_models_type_1 = UNSET + else: not_required_one_of_models_type_1 = ModelWithUnionProperty.from_dict(_not_required_one_of_models_type_1) return not_required_one_of_models_type_1 @@ -319,12 +321,13 @@ def _parse_not_required_nullable_one_of_models( if isinstance(data, Unset): return data try: - not_required_nullable_one_of_models_type_0: Union[Unset, FreeFormModel] if not isinstance(data, dict): raise TypeError() - not_required_nullable_one_of_models_type_0 = UNSET _not_required_nullable_one_of_models_type_0 = data - if not isinstance(_not_required_nullable_one_of_models_type_0, Unset): + not_required_nullable_one_of_models_type_0: Union[Unset, FreeFormModel] + if isinstance(_not_required_nullable_one_of_models_type_0, Unset): + not_required_nullable_one_of_models_type_0 = UNSET + else: not_required_nullable_one_of_models_type_0 = FreeFormModel.from_dict( _not_required_nullable_one_of_models_type_0 ) @@ -333,12 +336,13 @@ def _parse_not_required_nullable_one_of_models( except: # noqa: E722 pass try: - not_required_nullable_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] if not isinstance(data, dict): raise TypeError() - not_required_nullable_one_of_models_type_1 = UNSET _not_required_nullable_one_of_models_type_1 = data - if not isinstance(_not_required_nullable_one_of_models_type_1, Unset): + not_required_nullable_one_of_models_type_1: Union[Unset, ModelWithUnionProperty] + if isinstance(_not_required_nullable_one_of_models_type_1, Unset): + not_required_nullable_one_of_models_type_1 = UNSET + else: not_required_nullable_one_of_models_type_1 = ModelWithUnionProperty.from_dict( _not_required_nullable_one_of_models_type_1 ) @@ -352,19 +356,27 @@ def _parse_not_required_nullable_one_of_models( d.pop("not_required_nullable_one_of_models", UNSET) ) - nullable_model = None _nullable_model = d.pop("nullable_model") - if _nullable_model is not None: + nullable_model: Optional[ModelWithUnionProperty] + if _nullable_model is None: + nullable_model = None + else: nullable_model = ModelWithUnionProperty.from_dict(_nullable_model) - not_required_model: Union[Unset, ModelWithUnionProperty] = UNSET _not_required_model = d.pop("not_required_model", UNSET) - if not isinstance(_not_required_model, Unset): + not_required_model: Union[Unset, ModelWithUnionProperty] + if isinstance(_not_required_model, Unset): + not_required_model = UNSET + else: not_required_model = ModelWithUnionProperty.from_dict(_not_required_model) - not_required_nullable_model = None _not_required_nullable_model = d.pop("not_required_nullable_model", UNSET) - if _not_required_nullable_model is not None and not isinstance(_not_required_nullable_model, Unset): + not_required_nullable_model: Union[Unset, None, ModelWithUnionProperty] + if _not_required_nullable_model is None: + not_required_nullable_model = None + elif isinstance(_not_required_nullable_model, Unset): + not_required_nullable_model = UNSET + else: not_required_nullable_model = ModelWithUnionProperty.from_dict(_not_required_nullable_model) a_model = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py index ecfa97b10..08a016dd8 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_any_json_properties.py @@ -46,7 +46,6 @@ def _parse_additional_property( data: object, ) -> Union[List[str], ModelWithAnyJsonPropertiesAdditionalPropertyType0, bool, float, int, str]: try: - additional_property_type_0: ModelWithAnyJsonPropertiesAdditionalPropertyType0 if not isinstance(data, dict): raise TypeError() additional_property_type_0 = ModelWithAnyJsonPropertiesAdditionalPropertyType0.from_dict(data) @@ -55,7 +54,6 @@ def _parse_additional_property( except: # noqa: E722 pass try: - additional_property_type_1: List[str] if not isinstance(data, list): raise TypeError() additional_property_type_1 = cast(List[str], data) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py index 5dc264152..ee28313bd 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_primitive_additional_properties.py @@ -33,9 +33,11 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder] = UNSET _a_date_holder = d.pop("a_date_holder", UNSET) - if not isinstance(_a_date_holder, Unset): + a_date_holder: Union[Unset, ModelWithPrimitiveAdditionalPropertiesADateHolder] + if isinstance(_a_date_holder, Unset): + a_date_holder = UNSET + else: a_date_holder = ModelWithPrimitiveAdditionalPropertiesADateHolder.from_dict(_a_date_holder) model_with_primitive_additional_properties = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py index 6ebba75a6..e28a14e91 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_property_ref.py @@ -31,9 +31,11 @@ def to_dict(self) -> Dict[str, Any]: @classmethod def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: d = src_dict.copy() - inner: Union[Unset, ModelName] = UNSET _inner = d.pop("inner", UNSET) - if not isinstance(_inner, Unset): + inner: Union[Unset, ModelName] + if isinstance(_inner, Unset): + inner = UNSET + else: inner = ModelName.from_dict(_inner) model_with_property_ref = cls( diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py index 87034d5e7..b7fa116e3 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property.py @@ -44,12 +44,13 @@ def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: if isinstance(data, Unset): return data try: - a_property_type_0: Union[Unset, AnEnum] if not isinstance(data, str): raise TypeError() - a_property_type_0 = UNSET _a_property_type_0 = data - if not isinstance(_a_property_type_0, Unset): + a_property_type_0: Union[Unset, AnEnum] + if isinstance(_a_property_type_0, Unset): + a_property_type_0 = UNSET + else: a_property_type_0 = AnEnum(_a_property_type_0) return a_property_type_0 @@ -57,10 +58,11 @@ def _parse_a_property(data: object) -> Union[AnEnum, AnIntEnum, Unset]: pass if not isinstance(data, int): raise TypeError() - a_property_type_1: Union[Unset, AnIntEnum] - a_property_type_1 = UNSET _a_property_type_1 = data - if not isinstance(_a_property_type_1, Unset): + a_property_type_1: Union[Unset, AnIntEnum] + if isinstance(_a_property_type_1, Unset): + a_property_type_1 = UNSET + else: a_property_type_1 = AnIntEnum(_a_property_type_1) return a_property_type_1 diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py index 822f45cc2..27e91a80a 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/model_with_union_property_inlined.py @@ -46,12 +46,13 @@ def _parse_fruit( if isinstance(data, Unset): return data try: - fruit_type_0: Union[Unset, ModelWithUnionPropertyInlinedFruitType0] if not isinstance(data, dict): raise TypeError() - fruit_type_0 = UNSET _fruit_type_0 = data - if not isinstance(_fruit_type_0, Unset): + fruit_type_0: Union[Unset, ModelWithUnionPropertyInlinedFruitType0] + if isinstance(_fruit_type_0, Unset): + fruit_type_0 = UNSET + else: fruit_type_0 = ModelWithUnionPropertyInlinedFruitType0.from_dict(_fruit_type_0) return fruit_type_0 @@ -59,10 +60,11 @@ def _parse_fruit( pass if not isinstance(data, dict): raise TypeError() - fruit_type_1: Union[Unset, ModelWithUnionPropertyInlinedFruitType1] - fruit_type_1 = UNSET _fruit_type_1 = data - if not isinstance(_fruit_type_1, Unset): + fruit_type_1: Union[Unset, ModelWithUnionPropertyInlinedFruitType1] + if isinstance(_fruit_type_1, Unset): + fruit_type_1 = UNSET + else: fruit_type_1 = ModelWithUnionPropertyInlinedFruitType1.from_dict(_fruit_type_1) return fruit_type_1 diff --git a/openapi_python_client/templates/property_templates/property_macros.py.jinja b/openapi_python_client/templates/property_templates/property_macros.py.jinja index 92669cdc2..d578d1d4f 100644 --- a/openapi_python_client/templates/property_templates/property_macros.py.jinja +++ b/openapi_python_client/templates/property_templates/property_macros.py.jinja @@ -1,16 +1,20 @@ {% macro construct_template(construct_function, property, source, initial_value=None) %} {% if property.required and not property.nullable %} {{ property.python_name }} = {{ construct_function(property, source) }} -{% else %} -{% if initial_value != None %} -{{ property.python_name }} = {{ initial_value }} -{% elif property.nullable %} -{{ property.python_name }} = None -{% else %} -{{ property.python_name }}: {{ property.get_type_string() }} = UNSET -{% endif %} +{% else %}{# Must be nullable OR non-required #} _{{ property.python_name }} = {{ source }} -if {% if property.nullable %}_{{ property.python_name }} is not None{% endif %}{% if property.nullable and not property.required %} and {% endif %}{% if not property.required %}not isinstance(_{{ property.python_name }}, Unset){% endif %}: +{{ property.python_name }}: {{ property.get_type_string() }} + {% if property.nullable %} +if _{{ property.python_name }} is None: + {{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}None{% endif %} + + {% endif %} + {% if not property.required %} +{% if property.nullable %}elif{% else %}if{% endif %} isinstance(_{{ property.python_name }}, Unset): + {{ property.python_name }} = {% if initial_value != None %}{{ initial_value }}{% else %}UNSET{% endif %} + + {% endif %} +else: {{ property.python_name }} = {{ construct_function(property, "_" + property.python_name) }} {% endif %} {% endmacro %} diff --git a/openapi_python_client/templates/property_templates/union_property.py.jinja b/openapi_python_client/templates/property_templates/union_property.py.jinja index 684ae942d..87ea9820f 100644 --- a/openapi_python_client/templates/property_templates/union_property.py.jinja +++ b/openapi_python_client/templates/property_templates/union_property.py.jinja @@ -11,7 +11,6 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri {% for inner_property in property.inner_properties_with_template() %} {% if not loop.last or property.has_properties_without_templates %} try: - {{ inner_property.python_name }}: {{ inner_property.get_type_string() }} {% from "property_templates/" + inner_property.template import construct, check_type_for_construct %} if not {{ check_type_for_construct(inner_property, "data") }}: raise TypeError() @@ -23,7 +22,6 @@ def _parse_{{ property.python_name }}(data: object) -> {{ property.get_type_stri {% from "property_templates/" + inner_property.template import construct, check_type_for_construct %} if not {{ check_type_for_construct(inner_property, "data") }}: raise TypeError() - {{ inner_property.python_name }}: {{ inner_property.get_type_string() }} {{ construct(inner_property, "data", initial_value="UNSET") | indent(4) }} return {{ inner_property.python_name }} {% endif %} diff --git a/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py b/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py index 0b44852df..23208c971 100644 --- a/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py +++ b/tests/test_templates/test_property_templates/test_date_property/optional_nullable.py @@ -7,9 +7,13 @@ if not isinstance(some_source, Unset): some_destination = some_source.isoformat() if some_source else None -a_prop = None _a_prop = some_destination -if _a_prop is not None and not isinstance(_a_prop, Unset): +a_prop: Union[Unset, None, datetime.date] +if _a_prop is None: + a_prop = None +elif isinstance(_a_prop, Unset): + a_prop = UNSET +else: a_prop = isoparse(_a_prop).date() diff --git a/tests/test_templates/test_property_templates/test_date_property/required_nullable.py b/tests/test_templates/test_property_templates/test_date_property/required_nullable.py index f974c3210..79dd66ba4 100644 --- a/tests/test_templates/test_property_templates/test_date_property/required_nullable.py +++ b/tests/test_templates/test_property_templates/test_date_property/required_nullable.py @@ -4,9 +4,11 @@ from dateutil.parser import isoparse some_source = date(2020, 10, 12) some_destination = some_source.isoformat() if some_source else None -a_prop = None _a_prop = some_destination -if _a_prop is not None: +a_prop: Optional[datetime.date] +if _a_prop is None: + a_prop = None +else: a_prop = isoparse(_a_prop).date() From fddf68a1e7753638f41e01dfe1ff1871c7383517 Mon Sep 17 00:00:00 2001 From: Dylan Anthony <43723790+dbanty@users.noreply.github.com> Date: Wed, 12 May 2021 08:41:10 -0600 Subject: [PATCH 04/32] fix(parser): Prevent crash when providing a non-string default to a string attribute. Fixes #414 (#415) --- openapi_python_client/__main__.py | 4 ++-- openapi_python_client/parser/openapi.py | 9 ++++++++- openapi_python_client/parser/properties/converter.py | 8 +++++--- tests/test___main__.py | 4 ++-- tests/test_parser/test_openapi.py | 8 ++++++-- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/openapi_python_client/__main__.py b/openapi_python_client/__main__.py index 4cafccbaf..f8cd76f35 100644 --- a/openapi_python_client/__main__.py +++ b/openapi_python_client/__main__.py @@ -1,3 +1,3 @@ -from .cli import cli +from .cli import app -cli() +app() diff --git a/openapi_python_client/parser/openapi.py b/openapi_python_client/parser/openapi.py index ca8793d09..f0b9774ce 100644 --- a/openapi_python_client/parser/openapi.py +++ b/openapi_python_client/parser/openapi.py @@ -151,7 +151,14 @@ def _add_body( body=data.requestBody, schemas=schemas, parent_name=endpoint.name, config=config ) if isinstance(json_body, ParseError): - return ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=json_body.data), schemas + return ( + ParseError( + header=f"Cannot parse body of endpoint {endpoint.name}", + detail=json_body.detail, + data=json_body.data, + ), + schemas, + ) endpoint.multipart_body_class = Endpoint.parse_multipart_body(body=data.requestBody, config=config) diff --git a/openapi_python_client/parser/properties/converter.py b/openapi_python_client/parser/properties/converter.py index b493755fc..d8556e118 100644 --- a/openapi_python_client/parser/properties/converter.py +++ b/openapi_python_client/parser/properties/converter.py @@ -31,7 +31,7 @@ def convert(type_string: str, value: Any) -> Optional[Any]: raise ValidationError() try: return _CONVERTERS[type_string](value) - except (KeyError, ValueError) as e: + except (KeyError, ValueError, AttributeError) as e: raise ValidationError from e @@ -58,8 +58,10 @@ def convert_chain(type_strings: Iterable[str], value: Any) -> Optional[Any]: raise ValidationError() -def _convert_string(value: str) -> Optional[str]: - return f"{utils.remove_string_escapes(value)!r}" +def _convert_string(value: Any) -> Optional[str]: + if isinstance(value, str): + value = utils.remove_string_escapes(value) + return repr(value) def _convert_datetime(value: str) -> Optional[str]: diff --git a/tests/test___main__.py b/tests/test___main__.py index 4e2a51546..79ef06e0a 100644 --- a/tests/test___main__.py +++ b/tests/test___main__.py @@ -1,7 +1,7 @@ def test_main(mocker): - cli = mocker.patch("openapi_python_client.cli.cli") + app = mocker.patch("openapi_python_client.cli.app") # noinspection PyUnresolvedReferences from openapi_python_client import __main__ - cli.assert_called_once() + app.assert_called_once() diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index c496b7dc2..a220d243c 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -234,7 +234,7 @@ def test_add_body_bad_data(self, mocker): from openapi_python_client.parser.openapi import Endpoint, Schemas mocker.patch.object(Endpoint, "parse_request_form_body") - parse_error = ParseError(data=mocker.MagicMock()) + parse_error = ParseError(data=mocker.MagicMock(), detail=mocker.MagicMock()) other_schemas = mocker.MagicMock() mocker.patch.object(Endpoint, "parse_request_json_body", return_value=(parse_error, other_schemas)) endpoint = self.make_endpoint() @@ -249,7 +249,11 @@ def test_add_body_bad_data(self, mocker): ) assert result == ( - ParseError(detail=f"cannot parse body of endpoint {endpoint.name}", data=parse_error.data), + ParseError( + header=f"Cannot parse body of endpoint {endpoint.name}", + detail=parse_error.detail, + data=parse_error.data, + ), other_schemas, ) From 79acbf86b2ba191fa6b557803f79bc7dcd94707a Mon Sep 17 00:00:00 2001 From: Dylan Anthony <43723790+dbanty@users.noreply.github.com> Date: Wed, 12 May 2021 19:02:01 -0600 Subject: [PATCH 05/32] feat(parser): Allow references to non-object, non-enum types [#371][#418][#425]. Thanks @p1-ra! * parser / properties / do not restrict reference pointer type to `enum` or `object` * e2e / update `openapi.json` and golden-record * tpl / model / add missing typing import: `Tuple,Optional,BinaryIO,TextIO` * test: Make big ref test model properties required to lessen generated code Co-authored-by: Nementon --- .../my_test_api_client/models/__init__.py | 1 + ...roperties_reference_that_are_not_object.py | 341 +++++++++++++++ end_to_end_tests/openapi.json | 397 ++++++++++++++++-- .../parser/properties/schemas.py | 29 +- .../templates/model.py.jinja | 2 +- 5 files changed, 723 insertions(+), 47 deletions(-) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 5bbd77d7d..ea210a41f 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -1,6 +1,7 @@ """ Contains all the data models used in inputs/outputs """ from .a_model import AModel +from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject from .all_of_sub_model import AllOfSubModel from .an_all_of_enum import AnAllOfEnum from .an_enum import AnEnum diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py new file mode 100644 index 000000000..797b57b38 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py @@ -0,0 +1,341 @@ +import datetime +from io import BytesIO +from typing import Any, Dict, List, Type, TypeVar, cast + +import attr +from dateutil.parser import isoparse + +from ..models.an_enum import AnEnum +from ..types import File + +T = TypeVar("T", bound="AModelWithPropertiesReferenceThatAreNotObject") + + +@attr.s(auto_attribs=True) +class AModelWithPropertiesReferenceThatAreNotObject: + """ """ + + enum_properties_ref: List[AnEnum] + str_properties_ref: List[str] + date_properties_ref: List[datetime.date] + datetime_properties_ref: List[datetime.datetime] + int_32_properties_ref: List[int] + int_64_properties_ref: List[int] + float_properties_ref: List[float] + double_properties_ref: List[float] + file_properties_ref: List[File] + bytestream_properties_ref: List[str] + enum_properties: List[AnEnum] + str_properties: List[str] + date_properties: List[datetime.date] + datetime_properties: List[datetime.datetime] + int_32_properties: List[int] + int_64_properties: List[int] + float_properties: List[float] + double_properties: List[float] + file_properties: List[File] + bytestream_properties: List[str] + enum_property_ref: AnEnum + str_property_ref: str + date_property_ref: datetime.date + datetime_property_ref: datetime.datetime + int_32_property_ref: int + int_64_property_ref: int + float_property_ref: float + double_property_ref: float + file_property_ref: File + bytestream_property_ref: str + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + enum_properties_ref = [] + for componentsschemas_an_other_array_of_enum_item_data in self.enum_properties_ref: + componentsschemas_an_other_array_of_enum_item = componentsschemas_an_other_array_of_enum_item_data.value + + enum_properties_ref.append(componentsschemas_an_other_array_of_enum_item) + + str_properties_ref = self.str_properties_ref + + date_properties_ref = [] + for componentsschemas_an_other_array_of_date_item_data in self.date_properties_ref: + componentsschemas_an_other_array_of_date_item = ( + componentsschemas_an_other_array_of_date_item_data.isoformat() + ) + date_properties_ref.append(componentsschemas_an_other_array_of_date_item) + + datetime_properties_ref = [] + for componentsschemas_an_other_array_of_date_time_item_data in self.datetime_properties_ref: + componentsschemas_an_other_array_of_date_time_item = ( + componentsschemas_an_other_array_of_date_time_item_data.isoformat() + ) + + datetime_properties_ref.append(componentsschemas_an_other_array_of_date_time_item) + + int_32_properties_ref = self.int_32_properties_ref + + int_64_properties_ref = self.int_64_properties_ref + + float_properties_ref = self.float_properties_ref + + double_properties_ref = self.double_properties_ref + + file_properties_ref = [] + for componentsschemas_an_other_array_of_file_item_data in self.file_properties_ref: + componentsschemas_an_other_array_of_file_item = ( + componentsschemas_an_other_array_of_file_item_data.to_tuple() + ) + + file_properties_ref.append(componentsschemas_an_other_array_of_file_item) + + bytestream_properties_ref = self.bytestream_properties_ref + + enum_properties = [] + for componentsschemas_an_array_of_enum_item_data in self.enum_properties: + componentsschemas_an_array_of_enum_item = componentsschemas_an_array_of_enum_item_data.value + + enum_properties.append(componentsschemas_an_array_of_enum_item) + + str_properties = self.str_properties + + date_properties = [] + for componentsschemas_an_array_of_date_item_data in self.date_properties: + componentsschemas_an_array_of_date_item = componentsschemas_an_array_of_date_item_data.isoformat() + date_properties.append(componentsschemas_an_array_of_date_item) + + datetime_properties = [] + for componentsschemas_an_array_of_date_time_item_data in self.datetime_properties: + componentsschemas_an_array_of_date_time_item = componentsschemas_an_array_of_date_time_item_data.isoformat() + + datetime_properties.append(componentsschemas_an_array_of_date_time_item) + + int_32_properties = self.int_32_properties + + int_64_properties = self.int_64_properties + + float_properties = self.float_properties + + double_properties = self.double_properties + + file_properties = [] + for componentsschemas_an_array_of_file_item_data in self.file_properties: + componentsschemas_an_array_of_file_item = componentsschemas_an_array_of_file_item_data.to_tuple() + + file_properties.append(componentsschemas_an_array_of_file_item) + + bytestream_properties = self.bytestream_properties + + enum_property_ref = self.enum_property_ref.value + + str_property_ref = self.str_property_ref + date_property_ref = self.date_property_ref.isoformat() + datetime_property_ref = self.datetime_property_ref.isoformat() + + int_32_property_ref = self.int_32_property_ref + int_64_property_ref = self.int_64_property_ref + float_property_ref = self.float_property_ref + double_property_ref = self.double_property_ref + file_property_ref = self.file_property_ref.to_tuple() + + bytestream_property_ref = self.bytestream_property_ref + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "enum_properties_ref": enum_properties_ref, + "str_properties_ref": str_properties_ref, + "date_properties_ref": date_properties_ref, + "datetime_properties_ref": datetime_properties_ref, + "int32_properties_ref": int_32_properties_ref, + "int64_properties_ref": int_64_properties_ref, + "float_properties_ref": float_properties_ref, + "double_properties_ref": double_properties_ref, + "file_properties_ref": file_properties_ref, + "bytestream_properties_ref": bytestream_properties_ref, + "enum_properties": enum_properties, + "str_properties": str_properties, + "date_properties": date_properties, + "datetime_properties": datetime_properties, + "int32_properties": int_32_properties, + "int64_properties": int_64_properties, + "float_properties": float_properties, + "double_properties": double_properties, + "file_properties": file_properties, + "bytestream_properties": bytestream_properties, + "enum_property_ref": enum_property_ref, + "str_property_ref": str_property_ref, + "date_property_ref": date_property_ref, + "datetime_property_ref": datetime_property_ref, + "int32_property_ref": int_32_property_ref, + "int64_property_ref": int_64_property_ref, + "float_property_ref": float_property_ref, + "double_property_ref": double_property_ref, + "file_property_ref": file_property_ref, + "bytestream_property_ref": bytestream_property_ref, + } + ) + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + enum_properties_ref = [] + _enum_properties_ref = d.pop("enum_properties_ref") + for componentsschemas_an_other_array_of_enum_item_data in _enum_properties_ref: + componentsschemas_an_other_array_of_enum_item = AnEnum(componentsschemas_an_other_array_of_enum_item_data) + + enum_properties_ref.append(componentsschemas_an_other_array_of_enum_item) + + str_properties_ref = cast(List[str], d.pop("str_properties_ref")) + + date_properties_ref = [] + _date_properties_ref = d.pop("date_properties_ref") + for componentsschemas_an_other_array_of_date_item_data in _date_properties_ref: + componentsschemas_an_other_array_of_date_item = isoparse( + componentsschemas_an_other_array_of_date_item_data + ).date() + + date_properties_ref.append(componentsschemas_an_other_array_of_date_item) + + datetime_properties_ref = [] + _datetime_properties_ref = d.pop("datetime_properties_ref") + for componentsschemas_an_other_array_of_date_time_item_data in _datetime_properties_ref: + componentsschemas_an_other_array_of_date_time_item = isoparse( + componentsschemas_an_other_array_of_date_time_item_data + ) + + datetime_properties_ref.append(componentsschemas_an_other_array_of_date_time_item) + + int_32_properties_ref = cast(List[int], d.pop("int32_properties_ref")) + + int_64_properties_ref = cast(List[int], d.pop("int64_properties_ref")) + + float_properties_ref = cast(List[float], d.pop("float_properties_ref")) + + double_properties_ref = cast(List[float], d.pop("double_properties_ref")) + + file_properties_ref = [] + _file_properties_ref = d.pop("file_properties_ref") + for componentsschemas_an_other_array_of_file_item_data in _file_properties_ref: + componentsschemas_an_other_array_of_file_item = File( + payload=BytesIO(componentsschemas_an_other_array_of_file_item_data) + ) + + file_properties_ref.append(componentsschemas_an_other_array_of_file_item) + + bytestream_properties_ref = cast(List[str], d.pop("bytestream_properties_ref")) + + enum_properties = [] + _enum_properties = d.pop("enum_properties") + for componentsschemas_an_array_of_enum_item_data in _enum_properties: + componentsschemas_an_array_of_enum_item = AnEnum(componentsschemas_an_array_of_enum_item_data) + + enum_properties.append(componentsschemas_an_array_of_enum_item) + + str_properties = cast(List[str], d.pop("str_properties")) + + date_properties = [] + _date_properties = d.pop("date_properties") + for componentsschemas_an_array_of_date_item_data in _date_properties: + componentsschemas_an_array_of_date_item = isoparse(componentsschemas_an_array_of_date_item_data).date() + + date_properties.append(componentsschemas_an_array_of_date_item) + + datetime_properties = [] + _datetime_properties = d.pop("datetime_properties") + for componentsschemas_an_array_of_date_time_item_data in _datetime_properties: + componentsschemas_an_array_of_date_time_item = isoparse(componentsschemas_an_array_of_date_time_item_data) + + datetime_properties.append(componentsschemas_an_array_of_date_time_item) + + int_32_properties = cast(List[int], d.pop("int32_properties")) + + int_64_properties = cast(List[int], d.pop("int64_properties")) + + float_properties = cast(List[float], d.pop("float_properties")) + + double_properties = cast(List[float], d.pop("double_properties")) + + file_properties = [] + _file_properties = d.pop("file_properties") + for componentsschemas_an_array_of_file_item_data in _file_properties: + componentsschemas_an_array_of_file_item = File( + payload=BytesIO(componentsschemas_an_array_of_file_item_data) + ) + + file_properties.append(componentsschemas_an_array_of_file_item) + + bytestream_properties = cast(List[str], d.pop("bytestream_properties")) + + enum_property_ref = AnEnum(d.pop("enum_property_ref")) + + str_property_ref = d.pop("str_property_ref") + + date_property_ref = isoparse(d.pop("date_property_ref")).date() + + datetime_property_ref = isoparse(d.pop("datetime_property_ref")) + + int_32_property_ref = d.pop("int32_property_ref") + + int_64_property_ref = d.pop("int64_property_ref") + + float_property_ref = d.pop("float_property_ref") + + double_property_ref = d.pop("double_property_ref") + + file_property_ref = File(payload=BytesIO(d.pop("file_property_ref"))) + + bytestream_property_ref = d.pop("bytestream_property_ref") + + a_model_with_properties_reference_that_are_not_object = cls( + enum_properties_ref=enum_properties_ref, + str_properties_ref=str_properties_ref, + date_properties_ref=date_properties_ref, + datetime_properties_ref=datetime_properties_ref, + int_32_properties_ref=int_32_properties_ref, + int_64_properties_ref=int_64_properties_ref, + float_properties_ref=float_properties_ref, + double_properties_ref=double_properties_ref, + file_properties_ref=file_properties_ref, + bytestream_properties_ref=bytestream_properties_ref, + enum_properties=enum_properties, + str_properties=str_properties, + date_properties=date_properties, + datetime_properties=datetime_properties, + int_32_properties=int_32_properties, + int_64_properties=int_64_properties, + float_properties=float_properties, + double_properties=double_properties, + file_properties=file_properties, + bytestream_properties=bytestream_properties, + enum_property_ref=enum_property_ref, + str_property_ref=str_property_ref, + date_property_ref=date_property_ref, + datetime_property_ref=datetime_property_ref, + int_32_property_ref=int_32_property_ref, + int_64_property_ref=int_64_property_ref, + float_property_ref=float_property_ref, + double_property_ref=double_property_ref, + file_property_ref=file_property_ref, + bytestream_property_ref=bytestream_property_ref, + ) + + a_model_with_properties_reference_that_are_not_object.additional_properties = d + return a_model_with_properties_reference_that_are_not_object + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 585b97cde..64ccfdbc9 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -752,19 +752,25 @@ ], "get": { "responses": { - "200": {"description": "Success"} + "200": { + "description": "Success" + } } }, "post": { "responses": { - "200": {"description": "Success"} + "200": { + "description": "Success" + } } } }, "/same-name-multiple-locations/{param}": { "description": "Test that if you have a property of the same name in multiple locations, it produces valid code", "get": { - "tags": ["parameters"], + "tags": [ + "parameters" + ], "parameters": [ { "name": "param", @@ -893,10 +899,10 @@ "one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -904,10 +910,10 @@ "nullable_one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -915,10 +921,10 @@ "not_required_one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -926,10 +932,10 @@ "not_required_nullable_one_of_models": { "oneOf": [ { - "ref": "#/components/schemas/FreeFormModel" + "$ref": "#/components/schemas/FreeFormModel" }, { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" }, { "type": "string" @@ -940,7 +946,7 @@ "model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -948,7 +954,7 @@ "nullable_model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -956,7 +962,7 @@ "not_required_model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": false @@ -964,7 +970,7 @@ "not_required_nullable_model": { "allOf": [ { - "ref": "#/components/schemas/ModelWithUnionProperty" + "$ref": "#/components/schemas/ModelWithUnionProperty" } ], "nullable": true @@ -975,23 +981,37 @@ }, "AnEnum": { "title": "AnEnum", - "enum": ["FIRST_VALUE", "SECOND_VALUE"], + "enum": [ + "FIRST_VALUE", + "SECOND_VALUE" + ], "description": "For testing Enums in all the ways they can be used " }, "AnAllOfEnum": { "title": "AnAllOfEnum", - "enum": ["foo", "bar", "a_default", "overridden_default"], + "enum": [ + "foo", + "bar", + "a_default", + "overridden_default" + ], "default": "a_default" }, "AnIntEnum": { "title": "AnIntEnum", - "enum": [-1, 1, 2], + "enum": [ + -1, + 1, + 2 + ], "type": "integer", "description": "An enumeration." }, "Body_upload_file_tests_upload_post": { "title": "Body_upload_file_tests_upload_post", - "required": ["some_file"], + "required": [ + "some_file" + ], "type": "object", "properties": { "some_file": { @@ -1009,7 +1029,10 @@ }, "DifferentEnum": { "title": "DifferentEnum", - "enum": ["DIFFERENT", "OTHER"], + "enum": [ + "DIFFERENT", + "OTHER" + ], "description": "An enumeration." }, "HTTPValidationError": { @@ -1028,7 +1051,11 @@ }, "ValidationError": { "title": "ValidationError", - "required": ["loc", "msg", "type"], + "required": [ + "loc", + "msg", + "type" + ], "type": "object", "properties": { "loc": { @@ -1055,8 +1082,12 @@ "properties": { "a_property": { "oneOf": [ - {"$ref": "#/components/schemas/AnEnum"}, - {"$ref": "#/components/schemas/AnIntEnum"} + { + "$ref": "#/components/schemas/AnEnum" + }, + { + "$ref": "#/components/schemas/AnIntEnum" + } ] } }, @@ -1071,13 +1102,17 @@ { "type": "object", "properties": { - "apples": {"type": "string"} + "apples": { + "type": "string" + } } }, { "type": "object", "properties": { - "bananas": {"type": "string"} + "bananas": { + "type": "string" + } } } ] @@ -1099,7 +1134,9 @@ "additionalProperties": { "type": "object", "properties": { - "extra_props_prop": {"type": "string"} + "extra_props_prop": { + "type": "string" + } }, "additionalProperties": {} } @@ -1163,8 +1200,12 @@ "title": "ModelFromAllOf", "type": "object", "allOf": [ - {"$ref": "#/components/schemas/AllOfSubModel"}, - {"$ref": "#/components/schemas/AnotherAllOfSubModel"} + { + "$ref": "#/components/schemas/AllOfSubModel" + }, + { + "$ref": "#/components/schemas/AnotherAllOfSubModel" + } ] }, "AllOfSubModel": { @@ -1192,8 +1233,306 @@ "ModelWithPropertyRef": { "type": "object", "properties": { - "inner": {"$ref": "#/components/schemas/model_reference_doesnt_match"} + "inner": { + "$ref": "#/components/schemas/model_reference_doesnt_match" + } + } + }, + "AModelWithPropertiesReferenceThatAreNotObject": { + "type": "object", + "required": [ + "enum_properties_ref", + "str_properties_ref", + "date_properties_ref", + "datetime_properties_ref", + "int32_properties_ref", + "int64_properties_ref", + "float_properties_ref", + "double_properties_ref", + "file_properties_ref", + "bytestream_properties_ref", + "enum_properties", + "str_properties", + "date_properties", + "datetime_properties", + "int32_properties", + "int64_properties", + "float_properties", + "double_properties", + "file_properties", + "bytestream_properties", + "enum_property_ref", + "str_property_ref", + "date_property_ref", + "datetime_property_ref", + "int32_property_ref", + "int64_property_ref", + "float_property_ref", + "double_property_ref", + "file_property_ref", + "bytestream_property_ref" + ], + "properties": { + "enum_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfEnum" + }, + "str_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfString" + }, + "date_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDate" + }, + "datetime_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDateTime" + }, + "int32_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt32" + }, + "int64_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt64" + }, + "float_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFloat" + }, + "double_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDouble" + }, + "file_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFile" + }, + "bytestream_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfByteStream" + }, + "enum_properties": { + "$ref": "#/components/schemas/AnArrayOfEnum" + }, + "str_properties": { + "$ref": "#/components/schemas/AnArrayOfString" + }, + "date_properties": { + "$ref": "#/components/schemas/AnArrayOfDate" + }, + "datetime_properties": { + "$ref": "#/components/schemas/AnArrayOfDateTime" + }, + "int32_properties": { + "$ref": "#/components/schemas/AnArrayOfInt32" + }, + "int64_properties": { + "$ref": "#/components/schemas/AnArrayOfInt64" + }, + "float_properties": { + "$ref": "#/components/schemas/AnArrayOfFloat" + }, + "double_properties": { + "$ref": "#/components/schemas/AnArrayOfDouble" + }, + "file_properties": { + "$ref": "#/components/schemas/AnArrayOfFile" + }, + "bytestream_properties": { + "$ref": "#/components/schemas/AnArrayOfByteStream" + }, + "enum_property_ref": { + "$ref": "#/components/schemas/AnEnum" + }, + "str_property_ref": { + "$ref": "#/components/schemas/AString" + }, + "date_property_ref": { + "$ref": "#/components/schemas/ADate" + }, + "datetime_property_ref": { + "$ref": "#/components/schemas/ADateTime" + }, + "int32_property_ref": { + "$ref": "#/components/schemas/AnInt32" + }, + "int64_property_ref": { + "$ref": "#/components/schemas/AnInt64" + }, + "float_property_ref": { + "$ref": "#/components/schemas/AFloat" + }, + "double_property_ref": { + "$ref": "#/components/schemas/ADouble" + }, + "file_property_ref": { + "$ref": "#/components/schemas/AFile" + }, + "bytestream_property_ref": { + "$ref": "#/components/schemas/AByteStream" + } + } + }, + "AnArrayOfEnum": { + "type": "array", + "items": { + "title": "AnEnum", + "enum": [ + "FIRST_VALUE", + "SECOND_VALUE" + ], + "description": "For testing Enums in all the ways they can be used " + } + }, + "AnOtherArrayOfEnum": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnEnum" + } + }, + "AnArrayOfString": { + "type": "array", + "items": { + "type": "string" + } + }, + "AnOtherArrayOfString": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AString" + } + }, + "AString": { + "type": "string", + "pattern": "^helloworld.*" + }, + "AnArrayOfDate": { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + "AnOtherArrayOfDate": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADate" + } + }, + "ADate": { + "type": "string", + "format": "date" + }, + "AnArrayOfDateTime": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "AnOtherArrayOfDateTime": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADateTime" + } + }, + "ADateTime": { + "type": "string", + "format": "date-time" + }, + "AnArrayOfInt32": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" } + }, + "AnOtherArrayOfInt32": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt32" + } + }, + "AnInt32": { + "type": "integer", + "format": "int32" + }, + "AnArrayOfInt64": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "AnOtherArrayOfInt64": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt64" + } + }, + "AnInt64": { + "type": "integer", + "format": "int64" + }, + "AnArrayOfFloat": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfFloat": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFloat" + } + }, + "AFloat": { + "type": "number", + "format": "float" + }, + "AnArrayOfDouble": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfDouble": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADouble" + } + }, + "ADouble": { + "type": "number", + "format": "double" + }, + "AnArrayOfFile": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + }, + "AnOtherArrayOfFile": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFile" + } + }, + "AFile": { + "type": "string", + "format": "binary" + }, + "AnArrayOfByteStream": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + }, + "AnOtherArrayOfByteStream": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AByteStream" + } + }, + "AByteStream": { + "type": "string", + "format": "byte" } } } diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index a81879dfc..71ecf3843 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -11,11 +11,9 @@ from ..errors import ParseError, PropertyError if TYPE_CHECKING: # pragma: no cover - from .enum_property import EnumProperty - from .model_property import ModelProperty + from .property import Property else: - EnumProperty = "EnumProperty" - ModelProperty = "ModelProperty" + Property = "Property" _ReferencePath = NewType("_ReferencePath", str) @@ -58,26 +56,23 @@ def from_string(*, string: str, config: Config) -> "Class": class Schemas: """Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums)""" - classes_by_reference: Dict[_ReferencePath, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict) - classes_by_name: Dict[_ClassName, Union[EnumProperty, ModelProperty]] = attr.ib(factory=dict) + classes_by_reference: Dict[_ReferencePath, Property] = attr.ib(factory=dict) + classes_by_name: Dict[_ClassName, Property] = attr.ib(factory=dict) errors: List[ParseError] = attr.ib(factory=list) def update_schemas_with_data( *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config ) -> Union[Schemas, PropertyError]: - from . import build_enum_property, build_model_property - - prop: Union[PropertyError, ModelProperty, EnumProperty] - if data.enum is not None: - prop, schemas = build_enum_property( - data=data, name=ref_path, required=True, schemas=schemas, enum=data.enum, parent_name=None, config=config - ) - else: - prop, schemas = build_model_property( - data=data, name=ref_path, schemas=schemas, required=True, parent_name=None, config=config - ) + from . import property_from_data + + prop: Union[PropertyError, Property] + prop, schemas = property_from_data( + data=data, name=ref_path, schemas=schemas, required=True, parent_name="", config=config + ) + if isinstance(prop, PropertyError): return prop + schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference}) return schemas diff --git a/openapi_python_client/templates/model.py.jinja b/openapi_python_client/templates/model.py.jinja index 8541db32d..0cc98b105 100644 --- a/openapi_python_client/templates/model.py.jinja +++ b/openapi_python_client/templates/model.py.jinja @@ -1,4 +1,4 @@ -from typing import Any, Dict, Type, TypeVar +from typing import Any, Dict, Type, TypeVar, Tuple, Optional, BinaryIO, TextIO {% if model.additional_properties %} from typing import List From 0f58cfd4fc985ba871b3d2694d861c7f595dcb81 Mon Sep 17 00:00:00 2001 From: Dylan Anthony Date: Wed, 12 May 2021 19:15:19 -0600 Subject: [PATCH 06/32] chore: Prep 0.9.1 release --- CHANGELOG.md | 22 ++++++++++++++++++++++ dobby.toml | 6 +++--- pyproject.toml | 2 +- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2859fe41a..82fdf8bef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.9.1 + +### Features + +- Allow references to non-object, non-enum types [#371][#418][#425]. Thanks @p1-ra! +- Allow for attrs 21.x in generated clients [#412] +- Allow for using any version of Black [#416] [#411]. Thanks @christhekeele! + +### Fixes + +- Prevent crash when providing a non-string default to a string attribute. [#414] [#415] +- Deserialization of optional nullable properties when no value is returned from the API [#420] [#381]. Thanks @forest-benchling! + ## 0.9.0 - 2021-05-04 ### Breaking Changes @@ -45,9 +58,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - `none` will not create a project folder at all, only the inner package folder (which won't be inner anymore) - Attempt to detect and alert users if they are using an unsupported version of OpenAPI (#281). + - The media type application/vnd.api+json will now be handled just like application/json (#307). Thanks @jrversteegh! + - Support passing models into query parameters (#316). Thanks @forest-benchling! + - Add support for cookie parameters (#326). + - New `--file-encoding` command line option (#330). Sets the encoding used when writing generated files (defaults to utf-8). Thanks @dongfangtianyu! ### Changes @@ -96,13 +113,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Breaking Changes - Any request/response field that is not `required` and wasn't specified is now set to `UNSET` instead of `None`. + - Values that are `UNSET` will not be sent along in API calls + - Schemas defined with `type=object` will now be converted into classes, just like if they were created as ref components. The previous behavior was a combination of skipping and using generic Dicts for these schemas. + - Response schema handling was unified with input schema handling, meaning that responses will behave differently than before. Specifically, instead of the content-type deciding what the generated Python type is, the schema itself will. - As a result of this, endpoints that used to return `bytes` when content-type was application/octet-stream will now return a `File` object if the type of the data is "binary", just like if you were submitting that type instead of receiving it. - Instead of skipping input properties with no type, enum, anyOf, or oneOf declared, the property will be declared as `None`. + - Class (models and Enums) names will now contain the name of their parent element (if any). For example, a property declared in an endpoint will be named like {endpoint*name}*{previous*class*name}. Classes will no longer be deduplicated by appending a number to the end of the generated name, so if two names conflict with this new naming scheme, there will be an error instead. ### Additions @@ -268,6 +289,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Changes - The way most imports are handled was changed which _should_ lead to fewer unused imports in generated files. + - Better error messages - Most error messages will contain some useful information about why it failed instead of a stack trace diff --git a/dobby.toml b/dobby.toml index 2a0cc02fd..855ba9f73 100644 --- a/dobby.toml +++ b/dobby.toml @@ -1,5 +1,5 @@ [[workflows]] -name = "Start Task" +name = "task" [[workflows.steps]] type = "SelectGitHubIssue" @@ -7,13 +7,13 @@ name = "Start Task" type = "SwitchBranches" [[workflows]] -name = "Prepare Release" +name = "release" [[workflows.steps]] type = "UpdateProjectFromCommits" [[workflows.steps]] type = "Command" - command = "prettier --write CHANGELOG.md" + command = "npx prettier --write CHANGELOG.md" [github] owner = "triaxtec" diff --git a/pyproject.toml b/pyproject.toml index 6fb55a1a9..37847579b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "openapi-python-client" -version = "0.9.0" +version = "0.9.1" description = "Generate modern Python clients from OpenAPI" repository = "https://github.com/triaxtec/openapi-python-client" license = "MIT" From 588fa2369bc7ba20d82ef5b3b162b372ac848e3a Mon Sep 17 00:00:00 2001 From: p1-ra Date: Mon, 17 May 2021 16:05:16 +0200 Subject: [PATCH 07/32] templates / endpoint_module / form_data: do not use `attr.asdict`, use .to_dict()` serializer - to handle internal UNSET values to not serialize them, `attr.asdict` do not know UNSET type and will serialize them --- openapi_python_client/templates/endpoint_module.py.jinja | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openapi_python_client/templates/endpoint_module.py.jinja b/openapi_python_client/templates/endpoint_module.py.jinja index 06876141a..687705def 100644 --- a/openapi_python_client/templates/endpoint_module.py.jinja +++ b/openapi_python_client/templates/endpoint_module.py.jinja @@ -1,7 +1,6 @@ from typing import Any, Dict, List, Optional, Union, cast import httpx -from attr import asdict from ...client import AuthenticatedClient, Client from ...types import Response, UNSET{% if endpoint.multipart_body_class %}, File {% endif %} @@ -52,7 +51,7 @@ def _get_kwargs( "cookies": cookies, "timeout": client.get_timeout(), {% if endpoint.form_body_class %} - "data": asdict(form_data), + "data": form_data.to_dict(), {% elif endpoint.multipart_body_class %} "files": files, "data": data, From 1b17d1f282a7f7285c359496c0474a3eca35ad65 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Mon, 17 May 2021 16:40:49 +0200 Subject: [PATCH 08/32] e2e / update golden-record --- .../api/tests/post_form_data.py | 68 +++++++++++++++++++ .../my_test_api_client/models/__init__.py | 1 + .../my_test_api_client/models/a_form_data.py | 63 +++++++++++++++++ end_to_end_tests/openapi.json | 44 ++++++++++++ 4 files changed, 176 insertions(+) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py new file mode 100644 index 000000000..8152214c1 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/api/tests/post_form_data.py @@ -0,0 +1,68 @@ +from typing import Any, Dict + +import httpx + +from ...client import Client +from ...models.a_form_data import AFormData +from ...types import Response + + +def _get_kwargs( + *, + client: Client, + form_data: AFormData, +) -> Dict[str, Any]: + url = "{}/tests/post_form_data".format(client.base_url) + + headers: Dict[str, Any] = client.get_headers() + cookies: Dict[str, Any] = client.get_cookies() + + return { + "url": url, + "headers": headers, + "cookies": cookies, + "timeout": client.get_timeout(), + "data": form_data.to_dict(), + } + + +def _build_response(*, response: httpx.Response) -> Response[None]: + return Response( + status_code=response.status_code, + content=response.content, + headers=response.headers, + parsed=None, + ) + + +def sync_detailed( + *, + client: Client, + form_data: AFormData, +) -> Response[None]: + kwargs = _get_kwargs( + client=client, + form_data=form_data, + ) + + response = httpx.post( + **kwargs, + ) + + return _build_response(response=response) + + +async def asyncio_detailed( + *, + client: Client, + form_data: AFormData, +) -> Response[None]: + kwargs = _get_kwargs( + client=client, + form_data=form_data, + ) + + async with httpx.AsyncClient() as _client: + response = await _client.post(**kwargs) + + return _build_response(response=response) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index ea210a41f..0f7516048 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -1,5 +1,6 @@ """ Contains all the data models used in inputs/outputs """ +from .a_form_data import AFormData from .a_model import AModel from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject from .all_of_sub_model import AllOfSubModel diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py new file mode 100644 index 000000000..f5c34f5be --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_form_data.py @@ -0,0 +1,63 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AFormData") + + +@attr.s(auto_attribs=True) +class AFormData: + """ """ + + an_required_field: str + an_optional_field: Union[Unset, str] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + an_required_field = self.an_required_field + an_optional_field = self.an_optional_field + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "an_required_field": an_required_field, + } + ) + if an_optional_field is not UNSET: + field_dict["an_optional_field"] = an_optional_field + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + an_required_field = d.pop("an_required_field") + + an_optional_field = d.pop("an_optional_field", UNSET) + + a_form_data = cls( + an_required_field=an_required_field, + an_optional_field=an_optional_field, + ) + + a_form_data.additional_properties = d + return a_form_data + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 64ccfdbc9..bbd227358 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -188,6 +188,36 @@ } } }, + "/tests/post_form_data": { + "post": { + "tags": [ + "tests" + ], + "sumnary": "Post from data", + "description": "Post form data", + "operationId": "post_form_data", + "requestBody": { + "content": { + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/AFormData" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/json": { + "schema": {} + } + } + } + } + } + }, "/tests/upload": { "post": { "tags": [ @@ -797,6 +827,20 @@ }, "components": { "schemas": { + "AFormData": { + "type": "object", + "properties": { + "an_optional_field": { + "type": "string" + }, + "an_required_field": { + "type": "string" + } + }, + "required": [ + "an_required_field" + ] + }, "AModel": { "title": "AModel", "required": [ From 907208e4c175d1840214b74fdc84e1768e806da5 Mon Sep 17 00:00:00 2001 From: Nementon Date: Fri, 30 Apr 2021 15:23:23 +0200 Subject: [PATCH 09/32] templatize api,endpoint init files --- openapi_python_client/__init__.py | 21 ++++++++++++++++--- .../templates/api_init.py.jinja | 1 + .../templates/endpoint_init.py.jinja | 0 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 openapi_python_client/templates/api_init.py.jinja create mode 100644 openapi_python_client/templates/endpoint_init.py.jinja diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 2a7cf574b..35018d1cd 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -239,15 +239,30 @@ def _build_api(self) -> None: client_path.write_text(client_template.render(), encoding=self.file_encoding) # Generate endpoints + endpoint_collections_by_tag = self.openapi.endpoint_collections_by_tag.items() api_dir = self.package_dir / "api" api_dir.mkdir() - api_init = api_dir / "__init__.py" - api_init.write_text('""" Contains methods for accessing the API """', encoding=self.file_encoding) + api_init_path = api_dir / "__init__.py" + api_init_template = self.env.get_template("api_init.py.jinja") + api_init_path.write_text( + api_init_template.render( + package_name=self.package_name, + endpoint_collections_by_tag=endpoint_collections_by_tag, + ), + encoding=self.file_encoding, + ) endpoint_template = self.env.get_template("endpoint_module.py.jinja") - for tag, collection in self.openapi.endpoint_collections_by_tag.items(): + for tag, collection in endpoint_collections_by_tag: tag_dir = api_dir / tag tag_dir.mkdir() + + endpoint_init_path = tag_dir / "__init__.py" + endpoint_init_template = self.env.get_template("endpoint_init.py.jinja") + endpoint_init_path.write_text( + endpoint_init_template.render(package_name=self.package_name, tag=tag, endpoints=collection.endpoints), + encoding=self.file_encoding, + ) (tag_dir / "__init__.py").touch() for endpoint in collection.endpoints: diff --git a/openapi_python_client/templates/api_init.py.jinja b/openapi_python_client/templates/api_init.py.jinja new file mode 100644 index 000000000..dc035f4ce --- /dev/null +++ b/openapi_python_client/templates/api_init.py.jinja @@ -0,0 +1 @@ +""" Contains methods for accessing the API """ diff --git a/openapi_python_client/templates/endpoint_init.py.jinja b/openapi_python_client/templates/endpoint_init.py.jinja new file mode 100644 index 000000000..e69de29bb From 15c7b134388b40b938f134d77f6adc94faa703e9 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Mon, 3 May 2021 15:35:14 +0200 Subject: [PATCH 10/32] e2e / add {api_init.py.jina, endpoint_init.py.jinja} custom templates tests --- .../custom-templates-golden-record/README.md | 1 + .../my_test_api_client/api/__init__.py | 17 +++ .../api/default/__init__.py | 16 +++ .../my_test_api_client/api/tests/__init__.py | 129 ++++++++++++++++++ .../test_custom_templates/api_init.py.jinja | 19 +++ .../endpoint_init.py.jinja | 37 +++++ end_to_end_tests/test_end_to_end.py | 70 +++++++--- 7 files changed, 270 insertions(+), 19 deletions(-) create mode 100644 end_to_end_tests/custom-templates-golden-record/README.md create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py create mode 100644 end_to_end_tests/test_custom_templates/api_init.py.jinja create mode 100644 end_to_end_tests/test_custom_templates/endpoint_init.py.jinja diff --git a/end_to_end_tests/custom-templates-golden-record/README.md b/end_to_end_tests/custom-templates-golden-record/README.md new file mode 100644 index 000000000..e5106eea7 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/README.md @@ -0,0 +1 @@ +my-test-api-client \ No newline at end of file diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py new file mode 100644 index 000000000..359d0b2dc --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py @@ -0,0 +1,17 @@ +""" Contains methods for accessing the API """ + + +from typing import Type + +from my_test_api_client.api.default import DefaultEndpoints +from my_test_api_client.api.tests import TestsEndpoints + + +class MyTestApiClientApi: + @classmethod + def tests(cls) -> Type[TestsEndpoints]: + return TestsEndpoints + + @classmethod + def default(cls) -> Type[DefaultEndpoints]: + return DefaultEndpoints diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py new file mode 100644 index 000000000..5928666d9 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py @@ -0,0 +1,16 @@ +""" Contains methods for accessing the API Endpoints """ + + +import types + +from my_test_api_client.api.default import get_common_parameters, post_common_parameters + + +class DefaultEndpoints: + @classmethod + def get_common_parameters(cls) -> types.ModuleType: + return get_common_parameters + + @classmethod + def post_common_parameters(cls) -> types.ModuleType: + return post_common_parameters diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py new file mode 100644 index 000000000..b92538c28 --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py @@ -0,0 +1,129 @@ +""" Contains methods for accessing the API Endpoints """ + + +import types + +from my_test_api_client.api.tests import ( + defaults_tests_defaults_post, + get_basic_list_of_booleans, + get_basic_list_of_floats, + get_basic_list_of_integers, + get_basic_list_of_strings, + get_user_list, + int_enum_tests_int_enum_post, + json_body_tests_json_body_post, + no_response_tests_no_response_get, + octet_stream_tests_octet_stream_get, + optional_value_tests_optional_query_param, + test_inline_objects, + token_with_cookie_auth_token_with_cookie_get, + unsupported_content_tests_unsupported_content_get, + upload_file_tests_upload_post, +) + + +class TestsEndpoints: + @classmethod + def get_user_list(cls) -> types.ModuleType: + """ + Get a list of things + """ + return get_user_list + + @classmethod + def get_basic_list_of_strings(cls) -> types.ModuleType: + """ + Get a list of strings + """ + return get_basic_list_of_strings + + @classmethod + def get_basic_list_of_integers(cls) -> types.ModuleType: + """ + Get a list of integers + """ + return get_basic_list_of_integers + + @classmethod + def get_basic_list_of_floats(cls) -> types.ModuleType: + """ + Get a list of floats + """ + return get_basic_list_of_floats + + @classmethod + def get_basic_list_of_booleans(cls) -> types.ModuleType: + """ + Get a list of booleans + """ + return get_basic_list_of_booleans + + @classmethod + def upload_file_tests_upload_post(cls) -> types.ModuleType: + """ + Upload a file + """ + return upload_file_tests_upload_post + + @classmethod + def json_body_tests_json_body_post(cls) -> types.ModuleType: + """ + Try sending a JSON body + """ + return json_body_tests_json_body_post + + @classmethod + def defaults_tests_defaults_post(cls) -> types.ModuleType: + """ + Defaults + """ + return defaults_tests_defaults_post + + @classmethod + def octet_stream_tests_octet_stream_get(cls) -> types.ModuleType: + """ + Octet Stream + """ + return octet_stream_tests_octet_stream_get + + @classmethod + def no_response_tests_no_response_get(cls) -> types.ModuleType: + """ + No Response + """ + return no_response_tests_no_response_get + + @classmethod + def unsupported_content_tests_unsupported_content_get(cls) -> types.ModuleType: + """ + Unsupported Content + """ + return unsupported_content_tests_unsupported_content_get + + @classmethod + def int_enum_tests_int_enum_post(cls) -> types.ModuleType: + """ + Int Enum + """ + return int_enum_tests_int_enum_post + + @classmethod + def test_inline_objects(cls) -> types.ModuleType: + """ + Test Inline Objects + """ + return test_inline_objects + + @classmethod + def optional_value_tests_optional_query_param(cls) -> types.ModuleType: + """ + Test optional query parameters + """ + return optional_value_tests_optional_query_param + + @classmethod + def token_with_cookie_auth_token_with_cookie_get(cls) -> types.ModuleType: + """ + Test optional cookie parameters + """ + return token_with_cookie_auth_token_with_cookie_get diff --git a/end_to_end_tests/test_custom_templates/api_init.py.jinja b/end_to_end_tests/test_custom_templates/api_init.py.jinja new file mode 100644 index 000000000..06163930d --- /dev/null +++ b/end_to_end_tests/test_custom_templates/api_init.py.jinja @@ -0,0 +1,19 @@ +""" Contains methods for accessing the API """ + +{% macro snake_to_pascal_case(name) %} +{%- for part in name.split('_') -%} +{{ part | capitalize }} +{%- endfor -%} +{% endmacro %} + +from typing import Type +{% for tag, collection in endpoint_collections_by_tag %} +from {{ package_name }}.api.{{ tag }} import {{ snake_to_pascal_case(tag) }}Endpoints +{% endfor %} + +class {{ snake_to_pascal_case(package_name) }}Api: +{% for tag, collection in endpoint_collections_by_tag %} + @classmethod + def {{ tag }}(cls) -> Type[{{ snake_to_pascal_case(tag) }}Endpoints]: + return {{ snake_to_pascal_case(tag) }}Endpoints +{% endfor %} diff --git a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja new file mode 100644 index 000000000..20612ba43 --- /dev/null +++ b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja @@ -0,0 +1,37 @@ +""" Contains methods for accessing the API Endpoints """ + +{% macro snake_to_pascal_case(name) %} +{%- for part in name.split('_') -%} +{{ part | capitalize }} +{%- endfor -%} +{% endmacro %} + +{% macro to_snake_name(name) %} +{% set last_snakize = {'at': 0} %} +{%- for part in name -%} +{{ '_' if not loop.first and part in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' and (loop.index - last_snakize.at) > 1 and not last_snakize.update({'at': loop.index}) }}{{ part | lower }} +{%- endfor -%} +{% endmacro %} + +import types +{% for endpoint in endpoints %} +from {{ package_name }}.api.{{ tag }} import {{ to_snake_name(endpoint.name) }} +{% endfor %} + +class {{ snake_to_pascal_case(tag) }}Endpoints: + +{% for endpoint in endpoints %} + + @classmethod + def {{ to_snake_name(endpoint.name) }}(cls) -> types.ModuleType: + {% if endpoint.description %} + """ + {{ endpoint.description }} + """ + {% elif endpoint.summary %} + """ + {{ endpoint.summary }} + """ + {% endif %} + return {{ to_snake_name(endpoint.name) }} +{% endfor %} diff --git a/end_to_end_tests/test_end_to_end.py b/end_to_end_tests/test_end_to_end.py index fa4d21598..ad552092a 100644 --- a/end_to_end_tests/test_end_to_end.py +++ b/end_to_end_tests/test_end_to_end.py @@ -12,7 +12,10 @@ def _compare_directories( record: Path, test_subject: Path, - expected_differences: Optional[Dict[str, str]] = None, + expected_differences: Optional[ + Dict[str, str] + ] = None, # key: path relative to generated directory, value: expected generated content + depth=0, ): first_printable = record.relative_to(Path.cwd()) second_printable = test_subject.relative_to(Path.cwd()) @@ -22,27 +25,41 @@ def _compare_directories( pytest.fail(f"{first_printable} or {second_printable} was missing: {missing_files}", pytrace=False) expected_differences = expected_differences or {} - _, mismatch, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False) - mismatch = set(mismatch) - - for file_name in mismatch | set(expected_differences.keys()): - if file_name not in expected_differences: - continue - if file_name not in mismatch: - pytest.fail(f"Expected {file_name} to be different but it was not", pytrace=False) - generated = (test_subject / file_name).read_text() - assert generated == expected_differences[file_name], f"Unexpected output in {file_name}" - del expected_differences[file_name] - mismatch.remove(file_name) - - if mismatch: + _, mismatches, errors = cmpfiles(record, test_subject, dc.common_files, shallow=False) + mismatches = set(mismatches) + + expected_path_mismatches = [] + for file_name in mismatches: + + mismatch_file_path = test_subject.joinpath(file_name) + for expected_differences_path in expected_differences.keys(): + + if mismatch_file_path.match(str(expected_differences_path)): + + generated_content = (test_subject / file_name).read_text() + expected_content = expected_differences[expected_differences_path] + assert generated_content == expected_content, f"Unexpected output in {mismatch_file_path}" + expected_path_mismatches.append(expected_differences_path) + + for path_mismatch in expected_path_mismatches: + matched_file_name = path_mismatch.name + mismatches.remove(matched_file_name) + del expected_differences[path_mismatch] + + if mismatches: pytest.fail( - f"{first_printable} and {second_printable} had differing files: {mismatch}, and errors {errors}", + f"{first_printable} and {second_printable} had differing files: {mismatches}, and errors {errors}", pytrace=False, ) for sub_path in dc.common_dirs: - _compare_directories(record / sub_path, test_subject / sub_path, expected_differences=expected_differences) + _compare_directories( + record / sub_path, test_subject / sub_path, expected_differences=expected_differences, depth=depth + 1 + ) + + if depth == 0 and len(expected_differences.keys()) > 0: + failure = "\n".join([f"Expected {path} to be different but it was not" for path in expected_differences.keys()]) + pytest.fail(failure, pytrace=False) def run_e2e_test(extra_args=None, expected_differences=None): @@ -60,6 +77,7 @@ def run_e2e_test(extra_args=None, expected_differences=None): if result.exit_code != 0: raise result.exception + _compare_directories(gr_path, output_path, expected_differences=expected_differences) import mypy.api @@ -75,7 +93,21 @@ def test_end_to_end(): def test_custom_templates(): + expected_differences = {} # key: path relative to generated directory, value: expected generated content + expected_difference_paths = [ + Path("README.md"), + Path("my_test_api_client").joinpath("api", "__init__.py"), + Path("my_test_api_client").joinpath("api", "tests", "__init__.py"), + Path("my_test_api_client").joinpath("api", "default", "__init__.py"), + Path("my_test_api_client").joinpath("api", "parameters", "__init__.py"), + ] + + golden_tpls_root_dir = Path(__file__).parent.joinpath("custom-templates-golden-record") + for expected_difference_path in expected_difference_paths: + path = Path("my-test-api-client").joinpath(expected_difference_path) + expected_differences[path] = (golden_tpls_root_dir / expected_difference_path).read_text() + run_e2e_test( - extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates"], - expected_differences={"README.md": "my-test-api-client"}, + extra_args=["--custom-template-path=end_to_end_tests/test_custom_templates/"], + expected_differences=expected_differences, ) From df9ac8ab1551f0cc61661620aa6ddcf13c6a7fdd Mon Sep 17 00:00:00 2001 From: Nementon Date: Thu, 6 May 2021 08:52:13 +0200 Subject: [PATCH 11/32] expose utils module to jinja2 --- openapi_python_client/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openapi_python_client/__init__.py b/openapi_python_client/__init__.py index 35018d1cd..43f813483 100644 --- a/openapi_python_client/__init__.py +++ b/openapi_python_client/__init__.py @@ -67,6 +67,7 @@ def __init__( else: loader = package_loader self.env: Environment = Environment(loader=loader, trim_blocks=True, lstrip_blocks=True) + self.env.globals.update(utils=utils) self.project_name: str = config.project_name_override or f"{utils.kebab_case(openapi.title).lower()}-client" self.project_dir: Path = Path.cwd() From 8135d38cc7b80b83e93de3684dc32cc5005e178f Mon Sep 17 00:00:00 2001 From: Nementon Date: Thu, 6 May 2021 08:54:52 +0200 Subject: [PATCH 12/32] test_custom_templates / replace jinja2 marcro with call to utils module --- .../test_custom_templates/api_init.py.jinja | 14 ++++--------- .../endpoint_init.py.jinja | 21 ++++--------------- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/end_to_end_tests/test_custom_templates/api_init.py.jinja b/end_to_end_tests/test_custom_templates/api_init.py.jinja index 06163930d..3655db659 100644 --- a/end_to_end_tests/test_custom_templates/api_init.py.jinja +++ b/end_to_end_tests/test_custom_templates/api_init.py.jinja @@ -1,19 +1,13 @@ """ Contains methods for accessing the API """ -{% macro snake_to_pascal_case(name) %} -{%- for part in name.split('_') -%} -{{ part | capitalize }} -{%- endfor -%} -{% endmacro %} - from typing import Type {% for tag, collection in endpoint_collections_by_tag %} -from {{ package_name }}.api.{{ tag }} import {{ snake_to_pascal_case(tag) }}Endpoints +from {{ package_name }}.api.{{ tag }} import {{ utils.pascal_case(tag) }}Endpoints {% endfor %} -class {{ snake_to_pascal_case(package_name) }}Api: +class {{ utils.pascal_case(package_name) }}Api: {% for tag, collection in endpoint_collections_by_tag %} @classmethod - def {{ tag }}(cls) -> Type[{{ snake_to_pascal_case(tag) }}Endpoints]: - return {{ snake_to_pascal_case(tag) }}Endpoints + def {{ tag }}(cls) -> Type[{{ utils.pascal_case(tag) }}Endpoints]: + return {{ utils.pascal_case(tag) }}Endpoints {% endfor %} diff --git a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja index 20612ba43..fbbd40e4f 100644 --- a/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja +++ b/end_to_end_tests/test_custom_templates/endpoint_init.py.jinja @@ -1,29 +1,16 @@ """ Contains methods for accessing the API Endpoints """ -{% macro snake_to_pascal_case(name) %} -{%- for part in name.split('_') -%} -{{ part | capitalize }} -{%- endfor -%} -{% endmacro %} - -{% macro to_snake_name(name) %} -{% set last_snakize = {'at': 0} %} -{%- for part in name -%} -{{ '_' if not loop.first and part in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' and (loop.index - last_snakize.at) > 1 and not last_snakize.update({'at': loop.index}) }}{{ part | lower }} -{%- endfor -%} -{% endmacro %} - import types {% for endpoint in endpoints %} -from {{ package_name }}.api.{{ tag }} import {{ to_snake_name(endpoint.name) }} +from {{ package_name }}.api.{{ tag }} import {{ utils.snake_case(endpoint.name) }} {% endfor %} -class {{ snake_to_pascal_case(tag) }}Endpoints: +class {{ utils.pascal_case(tag) }}Endpoints: {% for endpoint in endpoints %} @classmethod - def {{ to_snake_name(endpoint.name) }}(cls) -> types.ModuleType: + def {{ utils.snake_case(endpoint.name) }}(cls) -> types.ModuleType: {% if endpoint.description %} """ {{ endpoint.description }} @@ -33,5 +20,5 @@ class {{ snake_to_pascal_case(tag) }}Endpoints: {{ endpoint.summary }} """ {% endif %} - return {{ to_snake_name(endpoint.name) }} + return {{ utils.snake_case(endpoint.name) }} {% endfor %} From 0b43462b68301b0e5a2654ec21ba5c92322935b4 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Mon, 3 May 2021 16:24:49 +0200 Subject: [PATCH 13/32] task / regen / regen custom template golden record --- .../my_test_api_client/api/__init__.py | 6 +- .../api/default/__init__.py | 1 - .../api/parameters/__init__.py | 11 ++++ .../my_test_api_client/api/tests/__init__.py | 1 - end_to_end_tests/regen_golden_record.py | 55 ++++++++++++++++++- 5 files changed, 70 insertions(+), 4 deletions(-) create mode 100644 end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py index 359d0b2dc..3ee5dbaf0 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/__init__.py @@ -1,9 +1,9 @@ """ Contains methods for accessing the API """ - from typing import Type from my_test_api_client.api.default import DefaultEndpoints +from my_test_api_client.api.parameters import ParametersEndpoints from my_test_api_client.api.tests import TestsEndpoints @@ -15,3 +15,7 @@ def tests(cls) -> Type[TestsEndpoints]: @classmethod def default(cls) -> Type[DefaultEndpoints]: return DefaultEndpoints + + @classmethod + def parameters(cls) -> Type[ParametersEndpoints]: + return ParametersEndpoints diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py index 5928666d9..4d0eb4fb5 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/default/__init__.py @@ -1,6 +1,5 @@ """ Contains methods for accessing the API Endpoints """ - import types from my_test_api_client.api.default import get_common_parameters, post_common_parameters diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py new file mode 100644 index 000000000..b92c6d96b --- /dev/null +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/parameters/__init__.py @@ -0,0 +1,11 @@ +""" Contains methods for accessing the API Endpoints """ + +import types + +from my_test_api_client.api.parameters import get_same_name_multiple_locations_param + + +class ParametersEndpoints: + @classmethod + def get_same_name_multiple_locations_param(cls) -> types.ModuleType: + return get_same_name_multiple_locations_param diff --git a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py index b92538c28..2cfd13809 100644 --- a/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py +++ b/end_to_end_tests/custom-templates-golden-record/my_test_api_client/api/tests/__init__.py @@ -1,6 +1,5 @@ """ Contains methods for accessing the API Endpoints """ - import types from my_test_api_client.api.tests import ( diff --git a/end_to_end_tests/regen_golden_record.py b/end_to_end_tests/regen_golden_record.py index aaa6aa850..1d4dc943d 100644 --- a/end_to_end_tests/regen_golden_record.py +++ b/end_to_end_tests/regen_golden_record.py @@ -1,12 +1,16 @@ """ Regenerate golden-record """ +import filecmp +import os import shutil +import tempfile from pathlib import Path from typer.testing import CliRunner from openapi_python_client.cli import app -if __name__ == "__main__": + +def regen_golden_record(): runner = CliRunner() openapi_path = Path(__file__).parent / "openapi.json" @@ -24,3 +28,52 @@ if result.exception: raise result.exception output_path.rename(gr_path) + + +def regen_custom_template_golden_record(): + runner = CliRunner() + openapi_path = Path(__file__).parent / "openapi.json" + tpl_dir = Path(__file__).parent / "test_custom_templates" + + gr_path = Path(__file__).parent / "golden-record" + tpl_gr_path = Path(__file__).parent / "custom-templates-golden-record" + + output_path = Path(tempfile.mkdtemp()) + config_path = Path(__file__).parent / "config.yml" + + shutil.rmtree(tpl_gr_path, ignore_errors=True) + + os.chdir(str(output_path.absolute())) + result = runner.invoke( + app, ["generate", f"--config={config_path}", f"--path={openapi_path}", f"--custom-template-path={tpl_dir}"] + ) + + if result.stdout: + generated_output_path = output_path / "my-test-api-client" + for f in generated_output_path.glob("**/*"): # nb: works for Windows and Unix + relative_to_generated = f.relative_to(generated_output_path) + gr_file = gr_path / relative_to_generated + if not gr_file.exists(): + print(f"{gr_file} does not exist, ignoring") + continue + + if not gr_file.is_file(): + continue + + if not filecmp.cmp(gr_file, f, shallow=False): + target_file = tpl_gr_path / relative_to_generated + target_dir = target_file.parent + + target_dir.mkdir(parents=True, exist_ok=True) + shutil.copy(f"{f}", f"{target_file}") + + shutil.rmtree(output_path, ignore_errors=True) + + if result.exception: + shutil.rmtree(output_path, ignore_errors=True) + raise result.exception + + +if __name__ == "__main__": + regen_golden_record() + regen_custom_template_golden_record() From 074b58d7943bbbf633239b8daaf2137c317624be Mon Sep 17 00:00:00 2001 From: Nementon Date: Thu, 13 May 2021 01:11:31 +0200 Subject: [PATCH 14/32] updated vendored openapi_schema_pydantic / correct oai false negative validation --- .../schema/openapi_schema_pydantic/README.md | 3 + .../openapi_schema_pydantic/__init__.py | 44 +++---- .../openapi_schema_pydantic/callback.py | 20 ++++ .../openapi_schema_pydantic/components.py | 7 +- .../schema/openapi_schema_pydantic/contact.py | 3 +- .../openapi_schema_pydantic/discriminator.py | 5 +- .../openapi_schema_pydantic/encoding.py | 17 +-- .../schema/openapi_schema_pydantic/example.py | 5 +- .../external_documentation.py | 5 +- .../schema/openapi_schema_pydantic/header.py | 3 +- .../schema/openapi_schema_pydantic/info.py | 7 +- .../schema/openapi_schema_pydantic/license.py | 5 +- .../schema/openapi_schema_pydantic/link.py | 7 +- .../openapi_schema_pydantic/media_type.py | 15 +-- .../openapi_schema_pydantic/oauth_flow.py | 9 +- .../openapi_schema_pydantic/oauth_flows.py | 9 +- .../openapi_schema_pydantic/open_api.py | 19 +-- .../openapi_schema_pydantic/operation.py | 24 ++-- .../openapi_schema_pydantic/parameter.py | 19 +-- .../openapi_schema_pydantic/path_item.py | 5 +- .../schema/openapi_schema_pydantic/paths.py | 3 +- .../openapi_schema_pydantic/reference.py | 3 +- .../openapi_schema_pydantic/request_body.py | 11 +- .../openapi_schema_pydantic/response.py | 9 +- .../openapi_schema_pydantic/responses.py | 3 +- .../schema/openapi_schema_pydantic/schema.py | 110 +++++++++--------- .../security_requirement.py | 1 + .../security_scheme.py | 9 +- .../schema/openapi_schema_pydantic/server.py | 9 +- .../server_variable.py | 9 +- .../schema/openapi_schema_pydantic/tag.py | 5 +- .../schema/openapi_schema_pydantic/xml.py | 3 +- 32 files changed, 239 insertions(+), 167 deletions(-) create mode 100644 openapi_python_client/schema/openapi_schema_pydantic/callback.py diff --git a/openapi_python_client/schema/openapi_schema_pydantic/README.md b/openapi_python_client/schema/openapi_schema_pydantic/README.md index 0e4d40146..f58b36909 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/README.md +++ b/openapi_python_client/schema/openapi_schema_pydantic/README.md @@ -1,5 +1,8 @@ Everything in this directory (including the rest of this file after this paragraph) is a vendored copy of [openapi-schem-pydantic](https://github.com/kuimono/openapi-schema-pydantic) and is licensed under the LICENSE file in this directory. +Included vendored version is the [following](https://github.com/kuimono/openapi-schema-pydantic/commit/0836b429086917feeb973de3367a7ac4c2b3a665) +Small patches has been applied to it. + ## Alias Due to the reserved words in python and pydantic, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/__init__.py b/openapi_python_client/schema/openapi_schema_pydantic/__init__.py index 9edb7d3d9..27fba924f 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/__init__.py @@ -38,32 +38,34 @@ "XML", ] -from .components import Components -from .contact import Contact -from .discriminator import Discriminator -from .encoding import Encoding -from .example import Example -from .external_documentation import ExternalDocumentation -from .header import Header + +from .open_api import OpenAPI from .info import Info +from .contact import Contact from .license import License -from .link import Link -from .media_type import MediaType -from .oauth_flow import OAuthFlow -from .oauth_flows import OAuthFlows -from .open_api import OpenAPI +from .server import Server +from .server_variable import ServerVariable +from .components import Components +from .paths import Paths +from .path_item import PathItem from .operation import Operation +from .external_documentation import ExternalDocumentation from .parameter import Parameter -from .path_item import PathItem -from .paths import Paths -from .reference import Reference from .request_body import RequestBody -from .response import Response +from .media_type import MediaType +from .encoding import Encoding from .responses import Responses -from .schema import Schema -from .security_requirement import SecurityRequirement -from .security_scheme import SecurityScheme -from .server import Server -from .server_variable import ServerVariable +from .response import Response +from .callback import Callback +from .example import Example +from .link import Link +from .header import Header from .tag import Tag +from .reference import Reference +from .schema import Schema +from .discriminator import Discriminator from .xml import XML +from .security_scheme import SecurityScheme +from .oauth_flows import OAuthFlows +from .oauth_flow import OAuthFlow +from .security_requirement import SecurityRequirement diff --git a/openapi_python_client/schema/openapi_schema_pydantic/callback.py b/openapi_python_client/schema/openapi_schema_pydantic/callback.py new file mode 100644 index 000000000..35bea75d7 --- /dev/null +++ b/openapi_python_client/schema/openapi_schema_pydantic/callback.py @@ -0,0 +1,20 @@ +from typing import Dict + + +Callback = Dict[str, "PathItem"] +""" +A map of possible out-of band callbacks related to the parent operation. +Each value in the map is a [Path Item Object](#pathItemObject) +that describes a set of requests that may be initiated by the API provider and the expected responses. +The key value used to identify the path item object is an expression, evaluated at runtime, +that identifies a URL to use for the callback operation. +""" + +"""Patterned Fields""" + +# {expression}: 'PathItem' = ... +""" +A Path Item Object used to define a callback request and expected responses. + +A [complete example](../examples/v3.0/callback-example.yaml) is available. +""" diff --git a/openapi_python_client/schema/openapi_schema_pydantic/components.py b/openapi_python_client/schema/openapi_schema_pydantic/components.py index 8ad888906..95b3a6e97 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/components.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/components.py @@ -1,7 +1,8 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra +from .callback import Callback from .example import Example from .header import Header from .link import Link @@ -44,7 +45,11 @@ class Components(BaseModel): links: Optional[Dict[str, Union[Link, Reference]]] = None """An object to hold reusable [Link Objects](#linkObject).""" + callbacks: Optional[Dict[str, Union[Callback, Reference]]] = None + """An object to hold reusable [Callback Objects](#callbackObject).""" + class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/contact.py b/openapi_python_client/schema/openapi_schema_pydantic/contact.py index c41aa8406..8cf450fbb 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/contact.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/contact.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class Contact(BaseModel): @@ -26,6 +26,7 @@ class Contact(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ {"name": "API Support", "url": "http://www.example.com/support", "email": "support@example.com"} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py index 1dfd06a5b..74338386d 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class Discriminator(BaseModel): @@ -14,7 +14,7 @@ class Discriminator(BaseModel): When using the discriminator, _inline_ schemas will not be considered. """ - propertyName: str + propertyName: str = ... """ **REQUIRED**. The name of the property in the payload that will hold the discriminator value. """ @@ -25,6 +25,7 @@ class Discriminator(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py index 9bf2ea177..d18447639 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py @@ -1,6 +1,6 @@ -from typing import Dict, Optional +from typing import Dict, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .reference import Reference @@ -12,20 +12,20 @@ class Encoding(BaseModel): """ The Content-Type for encoding a specific property. Default value depends on the property type: - + - for `string` with `format` being `binary` – `application/octet-stream`; - for other primitive types – `text/plain`; - for `object` - `application/json`; - for `array` – the default is defined based on the inner type. - + The value can be a specific media type (e.g. `application/json`), a wildcard media type (e.g. `image/*`), or a comma-separated list of the two types. """ - headers: Optional[Dict[str, Reference]] = None + headers: Optional[Dict[str, Union["Header", Reference]]] = None """ A map allowing additional information to be provided as headers, for example `Content-Disposition`. - + `Content-Type` is described separately and SHALL be ignored in this section. This property SHALL be ignored if the request body media type is not a `multipart`. """ @@ -33,7 +33,7 @@ class Encoding(BaseModel): style: Optional[str] = None """ Describes how a specific property value will be serialized depending on its type. - + See [Parameter Object](#parameterObject) for details on the [`style`](#parameterStyle) property. The behavior follows the same values as `query` parameters, including default values. This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`. @@ -43,7 +43,7 @@ class Encoding(BaseModel): """ When this is true, property values of type `array` or `object` generate separate parameters for each value of the array, or key-value-pair of the map. - + For other types of properties this property has no effect. When [`style`](#encodingStyle) is `form`, the default value is `true`. For all other styles, the default value is `false`. @@ -60,6 +60,7 @@ class Encoding(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/example.py b/openapi_python_client/schema/openapi_schema_pydantic/example.py index 6da3710b1..1eda5a11b 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/example.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/example.py @@ -1,6 +1,6 @@ from typing import Any, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class Example(BaseModel): @@ -28,11 +28,12 @@ class Example(BaseModel): """ A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents. - + The `value` field and `externalValue` field are mutually exclusive. """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ {"summary": "A foo example", "value": {"foo": "bar"}}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py index 92676a2ae..d2859b1aa 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class ExternalDocumentation(BaseModel): @@ -12,11 +12,12 @@ class ExternalDocumentation(BaseModel): [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ - url: AnyUrl + url: AnyUrl = ... """ **REQUIRED**. The URL for the target documentation. Value MUST be in the format of a URL. """ class Config: + extra = Extra.forbid schema_extra = {"examples": [{"description": "Find more info here", "url": "https://example.com"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/header.py b/openapi_python_client/schema/openapi_schema_pydantic/header.py index 5e59e8a66..f5ad792c9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/header.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/header.py @@ -1,4 +1,4 @@ -from pydantic import Field +from pydantic import Extra, Field from ..parameter_location import ParameterLocation from .parameter import Parameter @@ -18,6 +18,7 @@ class Header(Parameter): param_in = Field(default=ParameterLocation.HEADER, const=True, alias="in") class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/info.py b/openapi_python_client/schema/openapi_schema_pydantic/info.py index 36caba733..13376475c 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/info.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/info.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra from .contact import Contact from .license import License @@ -13,7 +13,7 @@ class Info(BaseModel): and MAY be presented in editing or documentation generation tools for convenience. """ - title: str + title: str = ... """ **REQUIRED**. The title of the API. """ @@ -40,13 +40,14 @@ class Info(BaseModel): The license information for the exposed API. """ - version: str + version: str = ... """ **REQUIRED**. The version of the OpenAPI document (which is distinct from the [OpenAPI Specification version](#oasVersion) or the API implementation version). """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/license.py b/openapi_python_client/schema/openapi_schema_pydantic/license.py index 567a5d117..d44267696 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/license.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/license.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class License(BaseModel): @@ -8,7 +8,7 @@ class License(BaseModel): License information for the exposed API. """ - name: str + name: str = ... """ **REQUIRED**. The license name used for the API. """ @@ -20,4 +20,5 @@ class License(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = {"examples": [{"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/link.py b/openapi_python_client/schema/openapi_schema_pydantic/link.py index 97bbc8aeb..174dca9b5 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/link.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/link.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .server import Server @@ -31,7 +31,7 @@ class Link(BaseModel): operationId: Optional[str] = None """ The name of an _existing_, resolvable OAS operation, as defined with a unique `operationId`. - + This field is mutually exclusive of the `operationRef` field. """ @@ -41,7 +41,7 @@ class Link(BaseModel): as specified with `operationId` or identified via `operationRef`. The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation. - + The parameter name can be qualified using the [parameter location](#parameterIn) `[{in}.]{name}` for operations that use the same parameter name in different locations (e.g. path.id). """ @@ -63,6 +63,7 @@ class Link(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ {"operationId": "getUserAddressByUUID", "parameters": {"userUuid": "$response.body#/uuid"}}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py index 7c00d3bc1..38319c5e9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field from .encoding import Encoding from .example import Example @@ -19,11 +19,11 @@ class MediaType(BaseModel): example: Optional[Any] = None """ Example of the media type. - + The example object SHOULD be in the correct format as specified by the media type. - + The `example` field is mutually exclusive of the `examples` field. - + Furthermore, if referencing a `schema` which contains an example, the `example` value SHALL _override_ the example provided by the schema. """ @@ -31,11 +31,11 @@ class MediaType(BaseModel): examples: Optional[Dict[str, Union[Example, Reference]]] = None """ Examples of the media type. - + Each example object SHOULD match the media type and specified schema if present. - + The `examples` field is mutually exclusive of the `example` field. - + Furthermore, if referencing a `schema` which contains an example, the `examples` value SHALL _override_ the example provided by the schema. """ @@ -49,6 +49,7 @@ class MediaType(BaseModel): """ class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py index cdf474bee..524be0f2b 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py @@ -1,6 +1,6 @@ -from typing import Dict, Optional +from typing import Dict, Optional, Union -from pydantic import AnyUrl, BaseModel +from pydantic import AnyUrl, BaseModel, Extra class OAuthFlow(BaseModel): @@ -15,7 +15,7 @@ class OAuthFlow(BaseModel): This MUST be in the form of a URL. """ - tokenUrl: Optional[str] = None + tokenUrl: Optional[AnyUrl] = None """ **REQUIRED** for `oauth2 ("password", "clientCredentials", "authorizationCode")`. The token URL to be used for this flow. @@ -27,7 +27,7 @@ class OAuthFlow(BaseModel): The URL to be used for obtaining refresh tokens. This MUST be in the form of a URL. """ - scopes: Dict[str, str] + scopes: Dict[str, str] = ... """ **REQUIRED**. The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it. @@ -35,6 +35,7 @@ class OAuthFlow(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py index fcb9ba348..c71370cd1 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .oauth_flow import OAuthFlow @@ -23,13 +23,16 @@ class OAuthFlows(BaseModel): clientCredentials: Optional[OAuthFlow] = None """ Configuration for the OAuth Client Credentials flow. - + Previously called `application` in OpenAPI 2.0. """ authorizationCode: Optional[OAuthFlow] = None """ Configuration for the OAuth Authorization Code flow. - + Previously called `accessCode` in OpenAPI 2.0. """ + + class Config: + extra = Extra.forbid diff --git a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py index dd480a8f5..a03080470 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .components import Components from .external_documentation import ExternalDocumentation @@ -14,19 +14,19 @@ class OpenAPI(BaseModel): """This is the root document object of the OpenAPI document.""" - info: Info + info: Info = ... """ **REQUIRED**. Provides metadata about the API. The metadata MAY be used by tooling as required. """ servers: List[Server] = [Server(url="/")] """ - An array of Server Objects, which provide connectivity information to a target server. + An array of Server Objects, which provide connectivity information to a target server. If the `servers` property is not provided, or is an empty array, the default value would be a [Server Object](#serverObject) with a [url](#serverUrl) value of `/`. """ - paths: Paths + paths: Paths = ... """ **REQUIRED**. The available paths and operations for the API. """ @@ -38,10 +38,10 @@ class OpenAPI(BaseModel): security: Optional[List[SecurityRequirement]] = None """ - A declaration of which security mechanisms can be used across the API. - The list of values includes alternative security requirement objects that can be used. - Only one of the security requirement objects need to be satisfied to authorize a request. - Individual operations can override this definition. + A declaration of which security mechanisms can be used across the API. + The list of values includes alternative security requirement objects that can be used. + Only one of the security requirement objects need to be satisfied to authorize a request. + Individual operations can override this definition. To make security optional, an empty security requirement (`{}`) can be included in the array. """ @@ -58,3 +58,6 @@ class OpenAPI(BaseModel): """ Additional external documentation. """ + + class Config: + extra = Extra.forbid diff --git a/openapi_python_client/schema/openapi_schema_pydantic/operation.py b/openapi_python_client/schema/openapi_schema_pydantic/operation.py index 860c3c24a..49150e6f7 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/operation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/operation.py @@ -1,7 +1,8 @@ -from typing import List, Optional, Union +from typing import Dict, List, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra +from .callback import Callback from .external_documentation import ExternalDocumentation from .parameter import Parameter from .reference import Reference @@ -58,18 +59,26 @@ class Operation(BaseModel): requestBody: Optional[Union[RequestBody, Reference]] = None """ - The request body applicable for this operation. - + The request body applicable for this operation. + The `requestBody` is only supported in HTTP methods where the HTTP 1.1 specification - [RFC7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) has explicitly defined semantics for request bodies. + [RFC7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague, `requestBody` SHALL be ignored by consumers. """ - responses: Responses + responses: Responses = ... """ **REQUIRED**. The list of possible responses as they are returned from executing this operation. """ + callbacks: Optional[Dict[str, Callback]] = None + """ + A map of possible out-of band callbacks related to the parent operation. + The key is a unique identifier for the Callback Object. + Each value in the map is a [Callback Object](#callbackObject) + that describes a request that may be initiated by the API provider and the expected responses. + """ + deprecated: bool = False """ Declares this operation to be deprecated. @@ -90,11 +99,12 @@ class Operation(BaseModel): servers: Optional[List[Server]] = None """ An alternative `server` array to service this operation. - If an alternative `server` object is specified at the Path Item Object or Root level, + If an alternative `server` object is specified at the Path Item Object or Root level, it will be overridden by this value. """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py index 52f1b6885..c4ffd5ef2 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, Extra from ..parameter_location import ParameterLocation from .example import Example @@ -18,15 +18,15 @@ class Parameter(BaseModel): """Fixed Fields""" - name: str + name: str = ... """ **REQUIRED**. The name of the parameter. - Parameter names are *case sensitive*. - - - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring + Parameter names are *case sensitive*. + + - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring within the [path](#pathsPath) field in the [Paths Object](#pathsObject). See [Path Templating](#pathTemplating) for further information. - - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`. + - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored. - For all other cases, the `name` corresponds to the parameter name used by the [`in`](#parameterIn) property. """ @@ -76,7 +76,7 @@ class Parameter(BaseModel): """ Describes how the parameter value will be serialized depending on the type of the parameter value. Default values (based on value of `in`): - + - for `query` - `form`; - for `path` - `simple`; - for `header` - `simple`; @@ -111,7 +111,7 @@ class Parameter(BaseModel): Example of the parameter's potential value. The example SHOULD match the specified schema and encoding properties if present. The `example` field is mutually exclusive of the `examples` field. - Furthermore, if referencing a `schema` that contains an example, + Furthermore, if referencing a `schema` that contains an example, the `example` value SHALL _override_ the example provided by the schema. To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary. @@ -127,7 +127,7 @@ class Parameter(BaseModel): """ """ - For more complex scenarios, the [`content`](#parameterContent) property + For more complex scenarios, the [`content`](#parameterContent) property can define the media type and schema of the parameter. A parameter MUST contain either a `schema` property, or a `content` property, but not both. When `example` or `examples` are provided in conjunction with the `schema` object, @@ -142,6 +142,7 @@ class Parameter(BaseModel): """ class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py index 911c1e805..da96ede56 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py @@ -1,6 +1,6 @@ from typing import List, Optional, Union -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field from .operation import Operation from .parameter import Parameter @@ -20,7 +20,7 @@ class PathItem(BaseModel): """ Allows for an external definition of this path item. The referenced structure MUST be in the format of a [Path Item Object](#pathItemObject). - + In case a Path Item Object field appears both in the defined object and the referenced object, the behavior is undefined. """ @@ -92,6 +92,7 @@ class PathItem(BaseModel): """ class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/paths.py b/openapi_python_client/schema/openapi_schema_pydantic/paths.py index 5f31b6e29..9e2b13c24 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/paths.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/paths.py @@ -2,6 +2,7 @@ from .path_item import PathItem + Paths = Dict[str, PathItem] """ Holds the relative paths to the individual endpoints and their operations. @@ -16,7 +17,7 @@ """ A relative path to an individual endpoint. The field name MUST begin with a forward slash (`/`). -The path is **appended** (no relative URL resolution) to the expanded URL +The path is **appended** (no relative URL resolution) to the expanded URL from the [`Server Object`](#serverObject)'s `url` field in order to construct the full URL. [Path templating](#pathTemplating) is allowed. When matching URLs, concrete (non-templated) paths would be matched before their templated counterparts. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py index 7803b3a54..54a73a290 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py @@ -1,4 +1,4 @@ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Extra, Field class Reference(BaseModel): @@ -16,6 +16,7 @@ class Reference(BaseModel): """**REQUIRED**. The reference string.""" class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [{"$ref": "#/components/schemas/Pet"}, {"$ref": "Pet.json"}, {"$ref": "definitions.json#/Pet"}] diff --git a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py index 626b795d7..308fc1ea3 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .media_type import MediaType @@ -11,17 +11,17 @@ class RequestBody(BaseModel): description: Optional[str] = None """ A brief description of the request body. - This could contain examples of use. - + This could contain examples of use. + [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ - content: Dict[str, MediaType] + content: Dict[str, MediaType] = ... """ **REQUIRED**. The content of the request body. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) and the value describes it. - + For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* """ @@ -31,6 +31,7 @@ class RequestBody(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/response.py b/openapi_python_client/schema/openapi_schema_pydantic/response.py index 8c8e539ec..f432faeb6 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/response.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/response.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Union -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .header import Header from .link import Link @@ -14,7 +14,7 @@ class Response(BaseModel): static `links` to operations based on the response. """ - description: str + description: str = ... """ **REQUIRED**. A short description of the response. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. @@ -31,8 +31,8 @@ class Response(BaseModel): """ A map containing descriptions of potential response payloads. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) - and the value describes it. - + and the value describes it. + For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* """ @@ -44,6 +44,7 @@ class Response(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/responses.py b/openapi_python_client/schema/openapi_schema_pydantic/responses.py index 6f3d9f8ce..980dcb9f7 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/responses.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/responses.py @@ -1,7 +1,8 @@ from typing import Dict, Union -from .reference import Reference from .response import Response +from .reference import Reference + Responses = Dict[str, Union[Response, Reference]] """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index 5941d79f5..c7898eb15 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -1,7 +1,6 @@ from typing import Any, Dict, List, Optional, Union -from pydantic import BaseModel, Field - +from pydantic import BaseModel, Extra, Field from .discriminator import Discriminator from .external_documentation import ExternalDocumentation from .reference import Reference @@ -36,7 +35,7 @@ class Schema(BaseModel): multipleOf: Optional[float] = Field(default=None, gt=0.0) """ The value of "multipleOf" MUST be a number, strictly greater than 0. - + A numeric instance is only valid if division by this keyword's value results in an integer. """ @@ -45,7 +44,7 @@ class Schema(BaseModel): """ The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. - + If the instance is a number, then this keyword validates if "exclusiveMaximum" is true and instance is less than the provided value, or else if the instance is less than or exactly equal to the @@ -57,7 +56,7 @@ class Schema(BaseModel): The value of "exclusiveMaximum" MUST be a boolean, representing whether the limit in "maximum" is exclusive or not. An undefined value is the same as false. - + If "exclusiveMaximum" is true, then a numeric instance SHOULD NOT be equal to the value specified in "maximum". If "exclusiveMaximum" is false (or not specified), then a numeric instance MAY be equal to the @@ -68,7 +67,7 @@ class Schema(BaseModel): """ The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. - + If the instance is a number, then this keyword validates if "exclusiveMinimum" is true and instance is greater than the provided value, or else if the instance is greater than or exactly equal to @@ -80,7 +79,7 @@ class Schema(BaseModel): The value of "exclusiveMinimum" MUST be a boolean, representing whether the limit in "minimum" is exclusive or not. An undefined value is the same as false. - + If "exclusiveMinimum" is true, then a numeric instance SHOULD NOT be equal to the value specified in "minimum". If "exclusiveMinimum" is false (or not specified), then a numeric instance MAY be equal to the @@ -93,10 +92,10 @@ class Schema(BaseModel): The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + A string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword. - + The length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159]. """ @@ -105,13 +104,13 @@ class Schema(BaseModel): """ A string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword. - + The length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159]. - + The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + "minLength", if absent, may be considered as being present with integer value 0. """ @@ -121,7 +120,7 @@ class Schema(BaseModel): The value of this keyword MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. - + A string instance is considered valid if the regular expression matches the instance successfully. Recall: regular expressions are not implicitly anchored. @@ -131,7 +130,7 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword. """ @@ -140,10 +139,10 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword. - + If this keyword is not present, it may be considered present with a value of 0. """ @@ -155,7 +154,7 @@ class Schema(BaseModel): If this keyword has boolean value false, the instance validates successfully. If it has boolean value true, the instance validates successfully if all of its elements are unique. - + If not present, this keyword may be considered present with boolean value false. """ @@ -164,7 +163,7 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the value of this keyword. """ @@ -173,10 +172,10 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, the value of this keyword. - + If this keyword is not present, it may be considered present with a value of 0. """ @@ -186,7 +185,7 @@ class Schema(BaseModel): The value of this keyword MUST be an array. This array MUST have at least one element. Elements of this array MUST be strings, and MUST be unique. - + An object instance is valid against this keyword if its property set contains all elements in this keyword's array value. """ @@ -195,9 +194,9 @@ class Schema(BaseModel): """ The value of this keyword MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique. - + Elements in the array MAY be of any type, including null. - + An instance validates successfully against this keyword if its value is equal to one of the elements in this keyword's array value. """ @@ -211,15 +210,15 @@ class Schema(BaseModel): """ **From OpenAPI spec: Value MUST be a string. Multiple types via an array are not supported.** - + From JSON Schema: The value of this keyword MUST be either a string or an array. If it is an array, elements of the array MUST be strings and MUST be unique. - + String values MUST be one of the seven primitive types defined by the core specification. - + An instance matches successfully if its primitive type is one of the types defined by keyword. Recall: "number" includes "integer". """ @@ -228,48 +227,48 @@ class Schema(BaseModel): """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an array. This array MUST have at least one element. - + Elements of the array MUST be objects. Each object MUST be a valid JSON Schema. - + An instance validates successfully against this keyword if it validates successfully against all schemas defined by this keyword's value. """ - oneOf: List[Union[Reference, "Schema"]] = [] + oneOf: Optional[List[Union[Reference, "Schema"]]] = None """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an array. This array MUST have at least one element. - + Elements of the array MUST be objects. Each object MUST be a valid JSON Schema. - + An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value. """ - anyOf: List[Union[Reference, "Schema"]] = [] + anyOf: Optional[List[Union[Reference, "Schema"]]] = None """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an array. This array MUST have at least one element. - + Elements of the array MUST be objects. Each object MUST be a valid JSON Schema. - + An instance validates successfully against this keyword if it validates successfully against at least one schema defined by this keyword's value. @@ -279,11 +278,11 @@ class Schema(BaseModel): """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an object. This object MUST be a valid JSON Schema. - + An instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword. """ @@ -294,13 +293,13 @@ class Schema(BaseModel): Value MUST be an object and not an array. Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema. `items` MUST be present if the `type` is `array`.** - + From JSON Schema: The value of "items" MUST be either a schema or array of schemas. Successful validation of an array instance with regards to these two keywords is determined as follows: - + - if "items" is not present, or its value is an object, validation of the instance always succeeds, regardless of the value of "additionalItems"; @@ -316,12 +315,12 @@ class Schema(BaseModel): **From OpenAPI spec: Property definitions MUST be a [Schema Object](#schemaObject) and not a standard JSON Schema (inline or referenced).** - + From JSON Schema: The value of "properties" MUST be an object. Each value of this object MUST be an object, and each object MUST be a valid JSON Schema. - + If absent, it can be considered the same as an empty object. """ @@ -331,19 +330,19 @@ class Schema(BaseModel): Value can be boolean or object. Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema. Consistent with JSON Schema, `additionalProperties` defaults to `true`.** - + From JSON Schema: The value of "additionalProperties" MUST be a boolean or a schema. If "additionalProperties" is absent, it may be considered present with an empty schema as a value. - + If "additionalProperties" is true, validation always succeeds. - + If "additionalProperties" is false, validation succeeds only if the instance is an object and all properties on the instance were covered by "properties" and/or "patternProperties". - + If "additionalProperties" is an object, validate the value as a schema to all of the properties that weren't validated by "properties" nor "patternProperties". @@ -353,7 +352,7 @@ class Schema(BaseModel): """ **From OpenAPI spec: [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.** - + From JSON Schema: The value "description" MUST be a string. @@ -368,7 +367,7 @@ class Schema(BaseModel): **From OpenAPI spec: [Data Type Formats](#dataTypeFormat) for further details. While relying on JSON Schema's defined formats, the OAS offers a few additional predefined formats.** - + From JSON Schema: Structural validation alone may be insufficient to validate that an instance meets all the requirements of an application. The "format" @@ -376,7 +375,7 @@ class Schema(BaseModel): fixed subset of values which are accurately described by authoritative resources, be they RFCs or other external specifications. - + The value of this keyword is called a format attribute. It MUST be a string. A format attribute can generally only validate a given set of instance types. If the type of the instance to validate is not in @@ -391,14 +390,14 @@ class Schema(BaseModel): as the value of the schema if one is not provided. Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level. For example, if `type` is `string`, then `default` can be `"foo"` but cannot be `1`.** - + From JSON Schema: There are no restrictions placed on the value of this keyword. - + This keyword can be used to supply a default JSON value associated with a particular schema. It is RECOMMENDED that a default value be valid against the associated schema. - + This keyword MAY be used in root schemas, and in any subschemas. """ @@ -406,7 +405,7 @@ class Schema(BaseModel): Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation: """ - nullable: bool = False + nullable: Optional[bool] = None """ A `true` value adds `"null"` to the allowed type specified by the `type` keyword, only if `type` is explicitly defined within the same Schema Object. @@ -466,12 +465,13 @@ class Schema(BaseModel): """ deprecated: Optional[bool] = None - """ + """ Specifies that a schema is deprecated and SHOULD be transitioned out of usage. Default value is `false`. """ class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py b/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py index b5165f4f9..4eeb19e57 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py @@ -1,5 +1,6 @@ from typing import Dict, List + SecurityRequirement = Dict[str, List[str]] """ Lists the required security schemes to execute this operation. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py index aee65bc2b..79ab33337 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import AnyUrl, BaseModel, Field +from pydantic import AnyUrl, BaseModel, Extra, Field from .oauth_flows import OAuthFlows @@ -15,7 +15,7 @@ class SecurityScheme(BaseModel): and [OpenID Connect Discovery](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06). """ - type: str + type: str = ... """ **REQUIRED**. The type of the security scheme. Valid values are `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. @@ -41,7 +41,7 @@ class SecurityScheme(BaseModel): """ **REQUIRED** for `http`. The name of the HTTP Authorization scheme to be used in the [Authorization header as defined in RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1). - + The values used SHOULD be registered in the [IANA Authentication Scheme registry](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml). """ @@ -49,7 +49,7 @@ class SecurityScheme(BaseModel): bearerFormat: Optional[str] = None """ A hint to the client to identify how the bearer token is formatted. - + Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes. """ @@ -66,6 +66,7 @@ class SecurityScheme(BaseModel): """ class Config: + extra = Extra.forbid allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server.py b/openapi_python_client/schema/openapi_schema_pydantic/server.py index 43c511f34..983d5a383 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server.py @@ -1,6 +1,6 @@ from typing import Dict, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .server_variable import ServerVariable @@ -8,10 +8,10 @@ class Server(BaseModel): """An object representing a Server.""" - url: str + url: str = ... """ **REQUIRED**. A URL to the target host. - + This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in `{`brackets`}`. @@ -26,11 +26,12 @@ class Server(BaseModel): variables: Optional[Dict[str, ServerVariable]] = None """ A map between a variable name and its value. - + The value is used for substitution in the server's URL template. """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ {"url": "https://development.gigantic-server.com/v1", "description": "Development server"}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py index 224c79411..fabb643c8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class ServerVariable(BaseModel): @@ -12,11 +12,11 @@ class ServerVariable(BaseModel): The array SHOULD NOT be empty. """ - default: str + default: str = ... """ **REQUIRED**. The default value to use for substitution, which SHALL be sent if an alternate value is _not_ supplied. - Note this behavior is different than the [Schema Object's](#schemaObject) treatment of default values, + Note this behavior is different than the [Schema Object's](#schemaObject) treatment of default values, because in those cases parameter values are optional. If the [`enum`](#serverVariableEnum) is defined, the value SHOULD exist in the enum's values. """ @@ -26,3 +26,6 @@ class ServerVariable(BaseModel): An optional description for the server variable. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ + + class Config: + extra = Extra.forbid diff --git a/openapi_python_client/schema/openapi_schema_pydantic/tag.py b/openapi_python_client/schema/openapi_schema_pydantic/tag.py index 531de02b4..8b6f21a0c 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/tag.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/tag.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra from .external_documentation import ExternalDocumentation @@ -11,7 +11,7 @@ class Tag(BaseModel): It is not mandatory to have a Tag Object per tag defined in the Operation Object instances. """ - name: str + name: str = ... """ **REQUIRED**. The name of the tag. """ @@ -28,4 +28,5 @@ class Tag(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = {"examples": [{"name": "pet", "description": "Pets operations"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/xml.py b/openapi_python_client/schema/openapi_schema_pydantic/xml.py index 9ddaf13e3..5feb02417 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/xml.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/xml.py @@ -1,6 +1,6 @@ from typing import Optional -from pydantic import BaseModel +from pydantic import BaseModel, Extra class XML(BaseModel): @@ -48,6 +48,7 @@ class XML(BaseModel): """ class Config: + extra = Extra.forbid schema_extra = { "examples": [ {"namespace": "http://example.com/schema/sample", "prefix": "sample"}, From 1e0620c092411bdb700f0b980b9c818b23392bd7 Mon Sep 17 00:00:00 2001 From: Nementon Date: Thu, 13 May 2021 13:08:23 +0200 Subject: [PATCH 15/32] vendored openapi_schema_pydantic / correct flake8 warnings - correct: W293, W291, F821 --- .../openapi_schema_pydantic/__init__.py | 45 ++++---- .../openapi_schema_pydantic/callback.py | 3 +- .../openapi_schema_pydantic/encoding.py | 12 +-- .../schema/openapi_schema_pydantic/example.py | 2 +- .../schema/openapi_schema_pydantic/link.py | 4 +- .../openapi_schema_pydantic/media_type.py | 12 +-- .../openapi_schema_pydantic/oauth_flow.py | 2 +- .../openapi_schema_pydantic/oauth_flows.py | 4 +- .../openapi_schema_pydantic/open_api.py | 10 +- .../openapi_schema_pydantic/operation.py | 10 +- .../openapi_schema_pydantic/parameter.py | 16 +-- .../openapi_schema_pydantic/path_item.py | 2 +- .../schema/openapi_schema_pydantic/paths.py | 3 +- .../openapi_schema_pydantic/request_body.py | 6 +- .../openapi_schema_pydantic/response.py | 4 +- .../openapi_schema_pydantic/responses.py | 3 +- .../schema/openapi_schema_pydantic/schema.py | 101 +++++++++--------- .../security_requirement.py | 1 - .../security_scheme.py | 4 +- .../schema/openapi_schema_pydantic/server.py | 4 +- .../server_variable.py | 2 +- 21 files changed, 124 insertions(+), 126 deletions(-) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/__init__.py b/openapi_python_client/schema/openapi_schema_pydantic/__init__.py index 27fba924f..6b02446a8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/__init__.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/__init__.py @@ -36,36 +36,37 @@ "ServerVariable", "Tag", "XML", + "Callback", ] -from .open_api import OpenAPI -from .info import Info +from .callback import Callback +from .components import Components from .contact import Contact +from .discriminator import Discriminator +from .encoding import Encoding +from .example import Example +from .external_documentation import ExternalDocumentation +from .header import Header +from .info import Info from .license import License -from .server import Server -from .server_variable import ServerVariable -from .components import Components -from .paths import Paths -from .path_item import PathItem +from .link import Link +from .media_type import MediaType +from .oauth_flow import OAuthFlow +from .oauth_flows import OAuthFlows +from .open_api import OpenAPI from .operation import Operation -from .external_documentation import ExternalDocumentation from .parameter import Parameter +from .path_item import PathItem +from .paths import Paths +from .reference import Reference from .request_body import RequestBody -from .media_type import MediaType -from .encoding import Encoding -from .responses import Responses from .response import Response -from .callback import Callback -from .example import Example -from .link import Link -from .header import Header -from .tag import Tag -from .reference import Reference +from .responses import Responses from .schema import Schema -from .discriminator import Discriminator -from .xml import XML -from .security_scheme import SecurityScheme -from .oauth_flows import OAuthFlows -from .oauth_flow import OAuthFlow from .security_requirement import SecurityRequirement +from .security_scheme import SecurityScheme +from .server import Server +from .server_variable import ServerVariable +from .tag import Tag +from .xml import XML diff --git a/openapi_python_client/schema/openapi_schema_pydantic/callback.py b/openapi_python_client/schema/openapi_schema_pydantic/callback.py index 35bea75d7..7607e95f3 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/callback.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/callback.py @@ -1,7 +1,6 @@ from typing import Dict - -Callback = Dict[str, "PathItem"] +Callback = Dict[str, "PathItem"] # noqa """ A map of possible out-of band callbacks related to the parent operation. Each value in the map is a [Path Item Object](#pathItemObject) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py index d18447639..b42c3c9a6 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py @@ -12,20 +12,20 @@ class Encoding(BaseModel): """ The Content-Type for encoding a specific property. Default value depends on the property type: - + - for `string` with `format` being `binary` – `application/octet-stream`; - for other primitive types – `text/plain`; - for `object` - `application/json`; - for `array` – the default is defined based on the inner type. - + The value can be a specific media type (e.g. `application/json`), a wildcard media type (e.g. `image/*`), or a comma-separated list of the two types. """ - headers: Optional[Dict[str, Union["Header", Reference]]] = None + headers: Optional[Dict[str, Union["Header", Reference]]] = None # noqa """ A map allowing additional information to be provided as headers, for example `Content-Disposition`. - + `Content-Type` is described separately and SHALL be ignored in this section. This property SHALL be ignored if the request body media type is not a `multipart`. """ @@ -33,7 +33,7 @@ class Encoding(BaseModel): style: Optional[str] = None """ Describes how a specific property value will be serialized depending on its type. - + See [Parameter Object](#parameterObject) for details on the [`style`](#parameterStyle) property. The behavior follows the same values as `query` parameters, including default values. This property SHALL be ignored if the request body media type is not `application/x-www-form-urlencoded`. @@ -43,7 +43,7 @@ class Encoding(BaseModel): """ When this is true, property values of type `array` or `object` generate separate parameters for each value of the array, or key-value-pair of the map. - + For other types of properties this property has no effect. When [`style`](#encodingStyle) is `form`, the default value is `true`. For all other styles, the default value is `false`. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/example.py b/openapi_python_client/schema/openapi_schema_pydantic/example.py index 1eda5a11b..206145f0a 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/example.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/example.py @@ -28,7 +28,7 @@ class Example(BaseModel): """ A URL that points to the literal example. This provides the capability to reference examples that cannot easily be included in JSON or YAML documents. - + The `value` field and `externalValue` field are mutually exclusive. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/link.py b/openapi_python_client/schema/openapi_schema_pydantic/link.py index 174dca9b5..bb0ff90b6 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/link.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/link.py @@ -31,7 +31,7 @@ class Link(BaseModel): operationId: Optional[str] = None """ The name of an _existing_, resolvable OAS operation, as defined with a unique `operationId`. - + This field is mutually exclusive of the `operationRef` field. """ @@ -41,7 +41,7 @@ class Link(BaseModel): as specified with `operationId` or identified via `operationRef`. The key is the parameter name to be used, whereas the value can be a constant or an expression to be evaluated and passed to the linked operation. - + The parameter name can be qualified using the [parameter location](#parameterIn) `[{in}.]{name}` for operations that use the same parameter name in different locations (e.g. path.id). """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py index 38319c5e9..690948ed8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py @@ -19,11 +19,11 @@ class MediaType(BaseModel): example: Optional[Any] = None """ Example of the media type. - + The example object SHOULD be in the correct format as specified by the media type. - + The `example` field is mutually exclusive of the `examples` field. - + Furthermore, if referencing a `schema` which contains an example, the `example` value SHALL _override_ the example provided by the schema. """ @@ -31,11 +31,11 @@ class MediaType(BaseModel): examples: Optional[Dict[str, Union[Example, Reference]]] = None """ Examples of the media type. - + Each example object SHOULD match the media type and specified schema if present. - + The `examples` field is mutually exclusive of the `example` field. - + Furthermore, if referencing a `schema` which contains an example, the `examples` value SHALL _override_ the example provided by the schema. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py index 524be0f2b..65286da57 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py @@ -1,4 +1,4 @@ -from typing import Dict, Optional, Union +from typing import Dict, Optional from pydantic import AnyUrl, BaseModel, Extra diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py index c71370cd1..642a8c850 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py @@ -23,14 +23,14 @@ class OAuthFlows(BaseModel): clientCredentials: Optional[OAuthFlow] = None """ Configuration for the OAuth Client Credentials flow. - + Previously called `application` in OpenAPI 2.0. """ authorizationCode: Optional[OAuthFlow] = None """ Configuration for the OAuth Authorization Code flow. - + Previously called `accessCode` in OpenAPI 2.0. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py index a03080470..e6ca4d929 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py @@ -21,7 +21,7 @@ class OpenAPI(BaseModel): servers: List[Server] = [Server(url="/")] """ - An array of Server Objects, which provide connectivity information to a target server. + An array of Server Objects, which provide connectivity information to a target server. If the `servers` property is not provided, or is an empty array, the default value would be a [Server Object](#serverObject) with a [url](#serverUrl) value of `/`. """ @@ -38,10 +38,10 @@ class OpenAPI(BaseModel): security: Optional[List[SecurityRequirement]] = None """ - A declaration of which security mechanisms can be used across the API. - The list of values includes alternative security requirement objects that can be used. - Only one of the security requirement objects need to be satisfied to authorize a request. - Individual operations can override this definition. + A declaration of which security mechanisms can be used across the API. + The list of values includes alternative security requirement objects that can be used. + Only one of the security requirement objects need to be satisfied to authorize a request. + Individual operations can override this definition. To make security optional, an empty security requirement (`{}`) can be included in the array. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/operation.py b/openapi_python_client/schema/openapi_schema_pydantic/operation.py index 49150e6f7..9f04ebd5d 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/operation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/operation.py @@ -59,10 +59,10 @@ class Operation(BaseModel): requestBody: Optional[Union[RequestBody, Reference]] = None """ - The request body applicable for this operation. - + The request body applicable for this operation. + The `requestBody` is only supported in HTTP methods where the HTTP 1.1 specification - [RFC7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) has explicitly defined semantics for request bodies. + [RFC7231](https://tools.ietf.org/html/rfc7231#section-4.3.1) has explicitly defined semantics for request bodies. In other cases where the HTTP spec is vague, `requestBody` SHALL be ignored by consumers. """ @@ -75,7 +75,7 @@ class Operation(BaseModel): """ A map of possible out-of band callbacks related to the parent operation. The key is a unique identifier for the Callback Object. - Each value in the map is a [Callback Object](#callbackObject) + Each value in the map is a [Callback Object](#callbackObject) that describes a request that may be initiated by the API provider and the expected responses. """ @@ -99,7 +99,7 @@ class Operation(BaseModel): servers: Optional[List[Server]] = None """ An alternative `server` array to service this operation. - If an alternative `server` object is specified at the Path Item Object or Root level, + If an alternative `server` object is specified at the Path Item Object or Root level, it will be overridden by this value. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py index c4ffd5ef2..cd52493e4 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py @@ -1,6 +1,6 @@ from typing import Any, Dict, Optional, Union -from pydantic import BaseModel, Field, Extra +from pydantic import BaseModel, Extra, Field from ..parameter_location import ParameterLocation from .example import Example @@ -21,12 +21,12 @@ class Parameter(BaseModel): name: str = ... """ **REQUIRED**. The name of the parameter. - Parameter names are *case sensitive*. - - - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring + Parameter names are *case sensitive*. + + - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring within the [path](#pathsPath) field in the [Paths Object](#pathsObject). See [Path Templating](#pathTemplating) for further information. - - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`, + - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`, the parameter definition SHALL be ignored. - For all other cases, the `name` corresponds to the parameter name used by the [`in`](#parameterIn) property. """ @@ -76,7 +76,7 @@ class Parameter(BaseModel): """ Describes how the parameter value will be serialized depending on the type of the parameter value. Default values (based on value of `in`): - + - for `query` - `form`; - for `path` - `simple`; - for `header` - `simple`; @@ -111,7 +111,7 @@ class Parameter(BaseModel): Example of the parameter's potential value. The example SHOULD match the specified schema and encoding properties if present. The `example` field is mutually exclusive of the `examples` field. - Furthermore, if referencing a `schema` that contains an example, + Furthermore, if referencing a `schema` that contains an example, the `example` value SHALL _override_ the example provided by the schema. To represent examples of media types that cannot naturally be represented in JSON or YAML, a string value can contain the example with escaping where necessary. @@ -127,7 +127,7 @@ class Parameter(BaseModel): """ """ - For more complex scenarios, the [`content`](#parameterContent) property + For more complex scenarios, the [`content`](#parameterContent) property can define the media type and schema of the parameter. A parameter MUST contain either a `schema` property, or a `content` property, but not both. When `example` or `examples` are provided in conjunction with the `schema` object, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py index da96ede56..8c6bc12f8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py @@ -20,7 +20,7 @@ class PathItem(BaseModel): """ Allows for an external definition of this path item. The referenced structure MUST be in the format of a [Path Item Object](#pathItemObject). - + In case a Path Item Object field appears both in the defined object and the referenced object, the behavior is undefined. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/paths.py b/openapi_python_client/schema/openapi_schema_pydantic/paths.py index 9e2b13c24..5f31b6e29 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/paths.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/paths.py @@ -2,7 +2,6 @@ from .path_item import PathItem - Paths = Dict[str, PathItem] """ Holds the relative paths to the individual endpoints and their operations. @@ -17,7 +16,7 @@ """ A relative path to an individual endpoint. The field name MUST begin with a forward slash (`/`). -The path is **appended** (no relative URL resolution) to the expanded URL +The path is **appended** (no relative URL resolution) to the expanded URL from the [`Server Object`](#serverObject)'s `url` field in order to construct the full URL. [Path templating](#pathTemplating) is allowed. When matching URLs, concrete (non-templated) paths would be matched before their templated counterparts. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py index 308fc1ea3..f4b598b29 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py @@ -11,8 +11,8 @@ class RequestBody(BaseModel): description: Optional[str] = None """ A brief description of the request body. - This could contain examples of use. - + This could contain examples of use. + [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ @@ -21,7 +21,7 @@ class RequestBody(BaseModel): **REQUIRED**. The content of the request body. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) and the value describes it. - + For requests that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/response.py b/openapi_python_client/schema/openapi_schema_pydantic/response.py index f432faeb6..335ae40ae 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/response.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/response.py @@ -31,8 +31,8 @@ class Response(BaseModel): """ A map containing descriptions of potential response payloads. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) - and the value describes it. - + and the value describes it. + For responses that match multiple keys, only the most specific key is applicable. e.g. text/plain overrides text/* """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/responses.py b/openapi_python_client/schema/openapi_schema_pydantic/responses.py index 980dcb9f7..6f3d9f8ce 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/responses.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/responses.py @@ -1,8 +1,7 @@ from typing import Dict, Union -from .response import Response from .reference import Reference - +from .response import Response Responses = Dict[str, Union[Response, Reference]] """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index c7898eb15..158ee15d7 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -1,6 +1,7 @@ from typing import Any, Dict, List, Optional, Union from pydantic import BaseModel, Extra, Field + from .discriminator import Discriminator from .external_documentation import ExternalDocumentation from .reference import Reference @@ -35,7 +36,7 @@ class Schema(BaseModel): multipleOf: Optional[float] = Field(default=None, gt=0.0) """ The value of "multipleOf" MUST be a number, strictly greater than 0. - + A numeric instance is only valid if division by this keyword's value results in an integer. """ @@ -44,7 +45,7 @@ class Schema(BaseModel): """ The value of "maximum" MUST be a number, representing an upper limit for a numeric instance. - + If the instance is a number, then this keyword validates if "exclusiveMaximum" is true and instance is less than the provided value, or else if the instance is less than or exactly equal to the @@ -56,7 +57,7 @@ class Schema(BaseModel): The value of "exclusiveMaximum" MUST be a boolean, representing whether the limit in "maximum" is exclusive or not. An undefined value is the same as false. - + If "exclusiveMaximum" is true, then a numeric instance SHOULD NOT be equal to the value specified in "maximum". If "exclusiveMaximum" is false (or not specified), then a numeric instance MAY be equal to the @@ -67,7 +68,7 @@ class Schema(BaseModel): """ The value of "minimum" MUST be a number, representing a lower limit for a numeric instance. - + If the instance is a number, then this keyword validates if "exclusiveMinimum" is true and instance is greater than the provided value, or else if the instance is greater than or exactly equal to @@ -79,7 +80,7 @@ class Schema(BaseModel): The value of "exclusiveMinimum" MUST be a boolean, representing whether the limit in "minimum" is exclusive or not. An undefined value is the same as false. - + If "exclusiveMinimum" is true, then a numeric instance SHOULD NOT be equal to the value specified in "minimum". If "exclusiveMinimum" is false (or not specified), then a numeric instance MAY be equal to the @@ -92,10 +93,10 @@ class Schema(BaseModel): The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + A string instance is valid against this keyword if its length is less than, or equal to, the value of this keyword. - + The length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159]. """ @@ -104,13 +105,13 @@ class Schema(BaseModel): """ A string instance is valid against this keyword if its length is greater than, or equal to, the value of this keyword. - + The length of a string instance is defined as the number of its characters as defined by RFC 7159 [RFC7159]. - + The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + "minLength", if absent, may be considered as being present with integer value 0. """ @@ -120,7 +121,7 @@ class Schema(BaseModel): The value of this keyword MUST be a string. This string SHOULD be a valid regular expression, according to the ECMA 262 regular expression dialect. - + A string instance is considered valid if the regular expression matches the instance successfully. Recall: regular expressions are not implicitly anchored. @@ -130,7 +131,7 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An array instance is valid against "maxItems" if its size is less than, or equal to, the value of this keyword. """ @@ -139,10 +140,10 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An array instance is valid against "minItems" if its size is greater than, or equal to, the value of this keyword. - + If this keyword is not present, it may be considered present with a value of 0. """ @@ -154,7 +155,7 @@ class Schema(BaseModel): If this keyword has boolean value false, the instance validates successfully. If it has boolean value true, the instance validates successfully if all of its elements are unique. - + If not present, this keyword may be considered present with boolean value false. """ @@ -163,7 +164,7 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An object instance is valid against "maxProperties" if its number of properties is less than, or equal to, the value of this keyword. """ @@ -172,10 +173,10 @@ class Schema(BaseModel): """ The value of this keyword MUST be an integer. This integer MUST be greater than, or equal to, 0. - + An object instance is valid against "minProperties" if its number of properties is greater than, or equal to, the value of this keyword. - + If this keyword is not present, it may be considered present with a value of 0. """ @@ -185,7 +186,7 @@ class Schema(BaseModel): The value of this keyword MUST be an array. This array MUST have at least one element. Elements of this array MUST be strings, and MUST be unique. - + An object instance is valid against this keyword if its property set contains all elements in this keyword's array value. """ @@ -194,9 +195,9 @@ class Schema(BaseModel): """ The value of this keyword MUST be an array. This array SHOULD have at least one element. Elements in the array SHOULD be unique. - + Elements in the array MAY be of any type, including null. - + An instance validates successfully against this keyword if its value is equal to one of the elements in this keyword's array value. """ @@ -210,15 +211,15 @@ class Schema(BaseModel): """ **From OpenAPI spec: Value MUST be a string. Multiple types via an array are not supported.** - + From JSON Schema: The value of this keyword MUST be either a string or an array. If it is an array, elements of the array MUST be strings and MUST be unique. - + String values MUST be one of the seven primitive types defined by the core specification. - + An instance matches successfully if its primitive type is one of the types defined by keyword. Recall: "number" includes "integer". """ @@ -227,14 +228,14 @@ class Schema(BaseModel): """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an array. This array MUST have at least one element. - + Elements of the array MUST be objects. Each object MUST be a valid JSON Schema. - + An instance validates successfully against this keyword if it validates successfully against all schemas defined by this keyword's value. @@ -244,14 +245,14 @@ class Schema(BaseModel): """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an array. This array MUST have at least one element. - + Elements of the array MUST be objects. Each object MUST be a valid JSON Schema. - + An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value. @@ -261,14 +262,14 @@ class Schema(BaseModel): """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an array. This array MUST have at least one element. - + Elements of the array MUST be objects. Each object MUST be a valid JSON Schema. - + An instance validates successfully against this keyword if it validates successfully against at least one schema defined by this keyword's value. @@ -278,11 +279,11 @@ class Schema(BaseModel): """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** - + From JSON Schema: This keyword's value MUST be an object. This object MUST be a valid JSON Schema. - + An instance is valid against this keyword if it fails to validate successfully against the schema defined by this keyword. """ @@ -293,13 +294,13 @@ class Schema(BaseModel): Value MUST be an object and not an array. Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema. `items` MUST be present if the `type` is `array`.** - + From JSON Schema: The value of "items" MUST be either a schema or array of schemas. Successful validation of an array instance with regards to these two keywords is determined as follows: - + - if "items" is not present, or its value is an object, validation of the instance always succeeds, regardless of the value of "additionalItems"; @@ -315,12 +316,12 @@ class Schema(BaseModel): **From OpenAPI spec: Property definitions MUST be a [Schema Object](#schemaObject) and not a standard JSON Schema (inline or referenced).** - + From JSON Schema: The value of "properties" MUST be an object. Each value of this object MUST be an object, and each object MUST be a valid JSON Schema. - + If absent, it can be considered the same as an empty object. """ @@ -330,19 +331,19 @@ class Schema(BaseModel): Value can be boolean or object. Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema. Consistent with JSON Schema, `additionalProperties` defaults to `true`.** - + From JSON Schema: The value of "additionalProperties" MUST be a boolean or a schema. If "additionalProperties" is absent, it may be considered present with an empty schema as a value. - + If "additionalProperties" is true, validation always succeeds. - + If "additionalProperties" is false, validation succeeds only if the instance is an object and all properties on the instance were covered by "properties" and/or "patternProperties". - + If "additionalProperties" is an object, validate the value as a schema to all of the properties that weren't validated by "properties" nor "patternProperties". @@ -352,7 +353,7 @@ class Schema(BaseModel): """ **From OpenAPI spec: [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.** - + From JSON Schema: The value "description" MUST be a string. @@ -367,7 +368,7 @@ class Schema(BaseModel): **From OpenAPI spec: [Data Type Formats](#dataTypeFormat) for further details. While relying on JSON Schema's defined formats, the OAS offers a few additional predefined formats.** - + From JSON Schema: Structural validation alone may be insufficient to validate that an instance meets all the requirements of an application. The "format" @@ -375,7 +376,7 @@ class Schema(BaseModel): fixed subset of values which are accurately described by authoritative resources, be they RFCs or other external specifications. - + The value of this keyword is called a format attribute. It MUST be a string. A format attribute can generally only validate a given set of instance types. If the type of the instance to validate is not in @@ -390,14 +391,14 @@ class Schema(BaseModel): as the value of the schema if one is not provided. Unlike JSON Schema, the value MUST conform to the defined type for the Schema Object defined at the same level. For example, if `type` is `string`, then `default` can be `"foo"` but cannot be `1`.** - + From JSON Schema: There are no restrictions placed on the value of this keyword. - + This keyword can be used to supply a default JSON value associated with a particular schema. It is RECOMMENDED that a default value be valid against the associated schema. - + This keyword MAY be used in root schemas, and in any subschemas. """ @@ -465,7 +466,7 @@ class Schema(BaseModel): """ deprecated: Optional[bool] = None - """ + """ Specifies that a schema is deprecated and SHOULD be transitioned out of usage. Default value is `false`. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py b/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py index 4eeb19e57..b5165f4f9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_requirement.py @@ -1,6 +1,5 @@ from typing import Dict, List - SecurityRequirement = Dict[str, List[str]] """ Lists the required security schemes to execute this operation. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py index 79ab33337..7c278afd9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py @@ -41,7 +41,7 @@ class SecurityScheme(BaseModel): """ **REQUIRED** for `http`. The name of the HTTP Authorization scheme to be used in the [Authorization header as defined in RFC7235](https://tools.ietf.org/html/rfc7235#section-5.1). - + The values used SHOULD be registered in the [IANA Authentication Scheme registry](https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml). """ @@ -49,7 +49,7 @@ class SecurityScheme(BaseModel): bearerFormat: Optional[str] = None """ A hint to the client to identify how the bearer token is formatted. - + Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server.py b/openapi_python_client/schema/openapi_schema_pydantic/server.py index 983d5a383..17802478f 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server.py @@ -11,7 +11,7 @@ class Server(BaseModel): url: str = ... """ **REQUIRED**. A URL to the target host. - + This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the OpenAPI document is being served. Variable substitutions will be made when a variable is named in `{`brackets`}`. @@ -26,7 +26,7 @@ class Server(BaseModel): variables: Optional[Dict[str, ServerVariable]] = None """ A map between a variable name and its value. - + The value is used for substitution in the server's URL template. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py index fabb643c8..4b30c3415 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py @@ -16,7 +16,7 @@ class ServerVariable(BaseModel): """ **REQUIRED**. The default value to use for substitution, which SHALL be sent if an alternate value is _not_ supplied. - Note this behavior is different than the [Schema Object's](#schemaObject) treatment of default values, + Note this behavior is different than the [Schema Object's](#schemaObject) treatment of default values, because in those cases parameter values are optional. If the [`enum`](#serverVariableEnum) is defined, the value SHOULD exist in the enum's values. """ From d8c1a9f3f8e83751db9845214149cb8e6c25a8a4 Mon Sep 17 00:00:00 2001 From: Nementon Date: Thu, 13 May 2021 01:51:33 +0200 Subject: [PATCH 16/32] correct openapi_schema_pydantic schemas breaking changes - anyOf, allOf, oneOf now default to `None` instead of `[]` --- .../parser/properties/__init__.py | 15 ++++++++++++--- .../parser/properties/property.py | 2 +- openapi_python_client/utils.py | 12 +++++++++++- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 908745a6a..6a5fa357f 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -9,7 +9,6 @@ "property_from_data", ] -from itertools import chain from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union import attr @@ -352,7 +351,13 @@ def build_union_property( *, data: oai.Schema, name: str, required: bool, schemas: Schemas, parent_name: str, config: Config ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]: sub_properties: List[Property] = [] - for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)): + + sub_data: List[Union[oai.Schema, oai.Reference]] = [] + for _data in [data.anyOf, data.oneOf]: + if isinstance(_data, Iterable): + sub_data.extend(_data) + + for i, sub_prop_data in enumerate(sub_data): sub_prop, schemas = property_from_data( name=f"{name}_type{i}", required=required, @@ -439,8 +444,12 @@ def _property_from_data( if isinstance(data, oai.Reference): return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas) + sub_data: List[Union[oai.Schema, oai.Reference]] = [] + for _data in [data.allOf, data.anyOf, data.oneOf]: + if isinstance(_data, Iterable): + sub_data.extend(_data) + # A union of a single reference should just be passed through to that reference (don't create copy class) - sub_data = (data.allOf or []) + data.anyOf + data.oneOf if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference): return _property_from_ref(name=name, required=required, parent=data, data=sub_data[0], schemas=schemas) diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py index d4ce54980..d82921377 100644 --- a/openapi_python_client/parser/properties/property.py +++ b/openapi_python_client/parser/properties/property.py @@ -22,7 +22,7 @@ class Property: name: str required: bool - nullable: bool + nullable: bool = attr.ib(converter=utils.optin_bool_to_bool_converter) _type_string: ClassVar[str] = "" _json_type_string: ClassVar[str] = "" # Type of the property after JSON serialization default: Optional[str] = attr.ib() diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py index 7a7c84185..e8338948f 100644 --- a/openapi_python_client/utils.py +++ b/openapi_python_client/utils.py @@ -1,7 +1,7 @@ import builtins import re from keyword import iskeyword -from typing import List +from typing import List, Optional delimiters = " _-" @@ -73,3 +73,13 @@ def to_valid_python_identifier(value: str) -> str: return new_value return f"{FIELD_PREFIX}{new_value}" + + +def optin_bool_to_bool_converter(value: Optional[bool]) -> bool: + """ + Given an Option[bool] return a bool + None are converted to False + """ + if value is None: + return False + return value From 5befdc538ac6b98b1d2e290bb398bfa6183a6799 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Thu, 13 May 2021 17:32:30 +0200 Subject: [PATCH 17/32] update unit tests --- tests/test_parser/test_openapi.py | 6 ++++-- tests/test_parser/test_properties/test_init.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index a220d243c..b135543c4 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -92,13 +92,15 @@ def test_swagger_document_invalid_schema(self, mocker): header="Failed to parse OpenAPI document", detail=( "You may be trying to use a Swagger document; this is not supported by this project.\n\n" - "3 validation errors for OpenAPI\n" + "4 validation errors for OpenAPI\n" "info\n" " field required (type=value_error.missing)\n" "paths\n" " field required (type=value_error.missing)\n" "openapi\n" - " field required (type=value_error.missing)" + " field required (type=value_error.missing)\n" + "swagger\n" + " extra fields not permitted (type=value_error.extra)" ), ) Schemas.build.assert_not_called() diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 1f5646d74..5666726a1 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -974,14 +974,14 @@ def test_property_from_data_union(self, mocker): name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() ) - FloatProperty.assert_called_once_with(name=name, required=required, default=0.0, nullable=False) - IntProperty.assert_called_once_with(name=name, required=required, default=0, nullable=False) + FloatProperty.assert_called_once_with(name=name, required=required, default=0.0, nullable=None) + IntProperty.assert_called_once_with(name=name, required=required, default=0, nullable=None) UnionProperty.assert_called_once_with( name=name, required=required, default=None, inner_properties=[FloatProperty.return_value, IntProperty.return_value], - nullable=False, + nullable=None, ) assert p == UnionProperty.return_value assert s == Schemas() From 470be5fac0f3302f9221c03d7cb962274c68eb97 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Thu, 13 May 2021 18:41:14 +0200 Subject: [PATCH 18/32] parser / properties / avoid call to isinstance for perf imprv --- openapi_python_client/parser/properties/__init__.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 6a5fa357f..1df7bea17 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -9,6 +9,7 @@ "property_from_data", ] +from itertools import chain from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union import attr @@ -352,12 +353,7 @@ def build_union_property( ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]: sub_properties: List[Property] = [] - sub_data: List[Union[oai.Schema, oai.Reference]] = [] - for _data in [data.anyOf, data.oneOf]: - if isinstance(_data, Iterable): - sub_data.extend(_data) - - for i, sub_prop_data in enumerate(sub_data): + for i, sub_prop_data in enumerate(chain(data.anyOf or [], data.oneOf or [])): sub_prop, schemas = property_from_data( name=f"{name}_type{i}", required=required, @@ -445,8 +441,8 @@ def _property_from_data( return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas) sub_data: List[Union[oai.Schema, oai.Reference]] = [] - for _data in [data.allOf, data.anyOf, data.oneOf]: - if isinstance(_data, Iterable): + for _data in (data.allOf, data.anyOf, data.oneOf): + if _data: sub_data.extend(_data) # A union of a single reference should just be passed through to that reference (don't create copy class) From b09ebd67cd6de68a1ece9bb460af3248f6385b26 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Thu, 13 May 2021 18:43:54 +0200 Subject: [PATCH 19/32] openapi_schema_pydantic / schema / set `nullable` type to `bool` --- openapi_python_client/parser/properties/property.py | 2 +- .../schema/openapi_schema_pydantic/schema.py | 2 +- openapi_python_client/utils.py | 12 +----------- tests/test_parser/test_properties/test_init.py | 6 +++--- 4 files changed, 6 insertions(+), 16 deletions(-) diff --git a/openapi_python_client/parser/properties/property.py b/openapi_python_client/parser/properties/property.py index d82921377..d4ce54980 100644 --- a/openapi_python_client/parser/properties/property.py +++ b/openapi_python_client/parser/properties/property.py @@ -22,7 +22,7 @@ class Property: name: str required: bool - nullable: bool = attr.ib(converter=utils.optin_bool_to_bool_converter) + nullable: bool _type_string: ClassVar[str] = "" _json_type_string: ClassVar[str] = "" # Type of the property after JSON serialization default: Optional[str] = attr.ib() diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index 158ee15d7..5a0cb1f91 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -406,7 +406,7 @@ class Schema(BaseModel): Other than the JSON Schema subset fields, the following fields MAY be used for further schema documentation: """ - nullable: Optional[bool] = None + nullable: bool = False """ A `true` value adds `"null"` to the allowed type specified by the `type` keyword, only if `type` is explicitly defined within the same Schema Object. diff --git a/openapi_python_client/utils.py b/openapi_python_client/utils.py index e8338948f..7a7c84185 100644 --- a/openapi_python_client/utils.py +++ b/openapi_python_client/utils.py @@ -1,7 +1,7 @@ import builtins import re from keyword import iskeyword -from typing import List, Optional +from typing import List delimiters = " _-" @@ -73,13 +73,3 @@ def to_valid_python_identifier(value: str) -> str: return new_value return f"{FIELD_PREFIX}{new_value}" - - -def optin_bool_to_bool_converter(value: Optional[bool]) -> bool: - """ - Given an Option[bool] return a bool - None are converted to False - """ - if value is None: - return False - return value diff --git a/tests/test_parser/test_properties/test_init.py b/tests/test_parser/test_properties/test_init.py index 5666726a1..1f5646d74 100644 --- a/tests/test_parser/test_properties/test_init.py +++ b/tests/test_parser/test_properties/test_init.py @@ -974,14 +974,14 @@ def test_property_from_data_union(self, mocker): name=name, required=required, data=data, schemas=Schemas(), parent_name="parent", config=MagicMock() ) - FloatProperty.assert_called_once_with(name=name, required=required, default=0.0, nullable=None) - IntProperty.assert_called_once_with(name=name, required=required, default=0, nullable=None) + FloatProperty.assert_called_once_with(name=name, required=required, default=0.0, nullable=False) + IntProperty.assert_called_once_with(name=name, required=required, default=0, nullable=False) UnionProperty.assert_called_once_with( name=name, required=required, default=None, inner_properties=[FloatProperty.return_value, IntProperty.return_value], - nullable=None, + nullable=False, ) assert p == UnionProperty.return_value assert s == Schemas() From e71c4ccf70d2113e6185195a9615f24f4b95057e Mon Sep 17 00:00:00 2001 From: p1-ra Date: Thu, 13 May 2021 18:51:04 +0200 Subject: [PATCH 20/32] openapi_schema_pydantic / remove ellipsis notation - Mypy do not well support it to declare `undefined` value --- .../schema/openapi_schema_pydantic/discriminator.py | 2 +- .../schema/openapi_schema_pydantic/external_documentation.py | 2 +- openapi_python_client/schema/openapi_schema_pydantic/info.py | 4 ++-- .../schema/openapi_schema_pydantic/license.py | 2 +- .../schema/openapi_schema_pydantic/oauth_flow.py | 2 +- .../schema/openapi_schema_pydantic/open_api.py | 4 ++-- .../schema/openapi_schema_pydantic/operation.py | 2 +- .../schema/openapi_schema_pydantic/parameter.py | 2 +- .../schema/openapi_schema_pydantic/request_body.py | 2 +- .../schema/openapi_schema_pydantic/response.py | 2 +- .../schema/openapi_schema_pydantic/security_scheme.py | 2 +- .../schema/openapi_schema_pydantic/server.py | 2 +- .../schema/openapi_schema_pydantic/server_variable.py | 2 +- openapi_python_client/schema/openapi_schema_pydantic/tag.py | 2 +- 14 files changed, 16 insertions(+), 16 deletions(-) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py index 74338386d..7174e1264 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py @@ -14,7 +14,7 @@ class Discriminator(BaseModel): When using the discriminator, _inline_ schemas will not be considered. """ - propertyName: str = ... + propertyName: str """ **REQUIRED**. The name of the property in the payload that will hold the discriminator value. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py index d2859b1aa..e99651690 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py @@ -12,7 +12,7 @@ class ExternalDocumentation(BaseModel): [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ - url: AnyUrl = ... + url: AnyUrl """ **REQUIRED**. The URL for the target documentation. Value MUST be in the format of a URL. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/info.py b/openapi_python_client/schema/openapi_schema_pydantic/info.py index 13376475c..95ffb5c89 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/info.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/info.py @@ -13,7 +13,7 @@ class Info(BaseModel): and MAY be presented in editing or documentation generation tools for convenience. """ - title: str = ... + title: str """ **REQUIRED**. The title of the API. """ @@ -40,7 +40,7 @@ class Info(BaseModel): The license information for the exposed API. """ - version: str = ... + version: str """ **REQUIRED**. The version of the OpenAPI document (which is distinct from the [OpenAPI Specification version](#oasVersion) or the API implementation version). diff --git a/openapi_python_client/schema/openapi_schema_pydantic/license.py b/openapi_python_client/schema/openapi_schema_pydantic/license.py index d44267696..363a06c47 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/license.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/license.py @@ -8,7 +8,7 @@ class License(BaseModel): License information for the exposed API. """ - name: str = ... + name: str """ **REQUIRED**. The license name used for the API. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py index 65286da57..50fd627b5 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py @@ -27,7 +27,7 @@ class OAuthFlow(BaseModel): The URL to be used for obtaining refresh tokens. This MUST be in the form of a URL. """ - scopes: Dict[str, str] = ... + scopes: Dict[str, str] """ **REQUIRED**. The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py index e6ca4d929..3927cdd96 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py @@ -14,7 +14,7 @@ class OpenAPI(BaseModel): """This is the root document object of the OpenAPI document.""" - info: Info = ... + info: Info """ **REQUIRED**. Provides metadata about the API. The metadata MAY be used by tooling as required. """ @@ -26,7 +26,7 @@ class OpenAPI(BaseModel): the default value would be a [Server Object](#serverObject) with a [url](#serverUrl) value of `/`. """ - paths: Paths = ... + paths: Paths """ **REQUIRED**. The available paths and operations for the API. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/operation.py b/openapi_python_client/schema/openapi_schema_pydantic/operation.py index 9f04ebd5d..620436214 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/operation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/operation.py @@ -66,7 +66,7 @@ class Operation(BaseModel): In other cases where the HTTP spec is vague, `requestBody` SHALL be ignored by consumers. """ - responses: Responses = ... + responses: Responses """ **REQUIRED**. The list of possible responses as they are returned from executing this operation. """ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py index cd52493e4..9e9d0674a 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py @@ -18,7 +18,7 @@ class Parameter(BaseModel): """Fixed Fields""" - name: str = ... + name: str """ **REQUIRED**. The name of the parameter. Parameter names are *case sensitive*. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py index f4b598b29..f15d41f31 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py @@ -16,7 +16,7 @@ class RequestBody(BaseModel): [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. """ - content: Dict[str, MediaType] = ... + content: Dict[str, MediaType] """ **REQUIRED**. The content of the request body. The key is a media type or [media type range](https://tools.ietf.org/html/rfc7231#appendix-D) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/response.py b/openapi_python_client/schema/openapi_schema_pydantic/response.py index 335ae40ae..152661e7a 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/response.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/response.py @@ -14,7 +14,7 @@ class Response(BaseModel): static `links` to operations based on the response. """ - description: str = ... + description: str """ **REQUIRED**. A short description of the response. [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py index 7c278afd9..868bb2c71 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py @@ -15,7 +15,7 @@ class SecurityScheme(BaseModel): and [OpenID Connect Discovery](https://tools.ietf.org/html/draft-ietf-oauth-discovery-06). """ - type: str = ... + type: str """ **REQUIRED**. The type of the security scheme. Valid values are `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server.py b/openapi_python_client/schema/openapi_schema_pydantic/server.py index 17802478f..b94fe6bbd 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server.py @@ -8,7 +8,7 @@ class Server(BaseModel): """An object representing a Server.""" - url: str = ... + url: str """ **REQUIRED**. A URL to the target host. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py index 4b30c3415..57254c2e1 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py @@ -12,7 +12,7 @@ class ServerVariable(BaseModel): The array SHOULD NOT be empty. """ - default: str = ... + default: str """ **REQUIRED**. The default value to use for substitution, which SHALL be sent if an alternate value is _not_ supplied. diff --git a/openapi_python_client/schema/openapi_schema_pydantic/tag.py b/openapi_python_client/schema/openapi_schema_pydantic/tag.py index 8b6f21a0c..e80e57b93 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/tag.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/tag.py @@ -11,7 +11,7 @@ class Tag(BaseModel): It is not mandatory to have a Tag Object per tag defined in the Operation Object instances. """ - name: str = ... + name: str """ **REQUIRED**. The name of the tag. """ From 2902c21b30bf42c148d87ca43b52f691bf1d71b4 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Thu, 13 May 2021 19:27:18 +0200 Subject: [PATCH 21/32] openapi_schema_pydantic / allow extra values - For support of OAI vendor extension --- .../schema/openapi_schema_pydantic/components.py | 2 +- .../schema/openapi_schema_pydantic/contact.py | 2 +- .../schema/openapi_schema_pydantic/discriminator.py | 2 +- .../schema/openapi_schema_pydantic/encoding.py | 2 +- .../schema/openapi_schema_pydantic/example.py | 2 +- .../openapi_schema_pydantic/external_documentation.py | 2 +- .../schema/openapi_schema_pydantic/header.py | 2 +- .../schema/openapi_schema_pydantic/info.py | 2 +- .../schema/openapi_schema_pydantic/license.py | 2 +- .../schema/openapi_schema_pydantic/link.py | 2 +- .../schema/openapi_schema_pydantic/media_type.py | 2 +- .../schema/openapi_schema_pydantic/oauth_flow.py | 2 +- .../schema/openapi_schema_pydantic/oauth_flows.py | 2 +- .../schema/openapi_schema_pydantic/open_api.py | 2 +- .../schema/openapi_schema_pydantic/operation.py | 2 +- .../schema/openapi_schema_pydantic/parameter.py | 2 +- .../schema/openapi_schema_pydantic/path_item.py | 2 +- .../schema/openapi_schema_pydantic/reference.py | 2 +- .../schema/openapi_schema_pydantic/request_body.py | 2 +- .../schema/openapi_schema_pydantic/response.py | 2 +- .../schema/openapi_schema_pydantic/schema.py | 2 +- .../schema/openapi_schema_pydantic/security_scheme.py | 2 +- .../schema/openapi_schema_pydantic/server.py | 2 +- .../schema/openapi_schema_pydantic/server_variable.py | 2 +- openapi_python_client/schema/openapi_schema_pydantic/tag.py | 2 +- openapi_python_client/schema/openapi_schema_pydantic/xml.py | 2 +- tests/test_parser/test_openapi.py | 6 ++---- 27 files changed, 28 insertions(+), 30 deletions(-) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/components.py b/openapi_python_client/schema/openapi_schema_pydantic/components.py index 95b3a6e97..a9a2f0339 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/components.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/components.py @@ -49,7 +49,7 @@ class Components(BaseModel): """An object to hold reusable [Callback Objects](#callbackObject).""" class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/contact.py b/openapi_python_client/schema/openapi_schema_pydantic/contact.py index 8cf450fbb..cbe2fb8eb 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/contact.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/contact.py @@ -26,7 +26,7 @@ class Contact(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ {"name": "API Support", "url": "http://www.example.com/support", "email": "support@example.com"} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py index 7174e1264..c2deb5a17 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/discriminator.py @@ -25,7 +25,7 @@ class Discriminator(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py index b42c3c9a6..ee0373469 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py @@ -60,7 +60,7 @@ class Encoding(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/example.py b/openapi_python_client/schema/openapi_schema_pydantic/example.py index 206145f0a..4ac234b37 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/example.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/example.py @@ -33,7 +33,7 @@ class Example(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ {"summary": "A foo example", "value": {"foo": "bar"}}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py index e99651690..467c98150 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/external_documentation.py @@ -19,5 +19,5 @@ class ExternalDocumentation(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = {"examples": [{"description": "Find more info here", "url": "https://example.com"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/header.py b/openapi_python_client/schema/openapi_schema_pydantic/header.py index f5ad792c9..8d6ee2ff1 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/header.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/header.py @@ -18,7 +18,7 @@ class Header(Parameter): param_in = Field(default=ParameterLocation.HEADER, const=True, alias="in") class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/info.py b/openapi_python_client/schema/openapi_schema_pydantic/info.py index 95ffb5c89..fcff5b742 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/info.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/info.py @@ -47,7 +47,7 @@ class Info(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/license.py b/openapi_python_client/schema/openapi_schema_pydantic/license.py index 363a06c47..7e8b7f3d9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/license.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/license.py @@ -20,5 +20,5 @@ class License(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = {"examples": [{"name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/link.py b/openapi_python_client/schema/openapi_schema_pydantic/link.py index bb0ff90b6..de1403a8c 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/link.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/link.py @@ -63,7 +63,7 @@ class Link(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ {"operationId": "getUserAddressByUUID", "parameters": {"userUuid": "$response.body#/uuid"}}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py index 690948ed8..387f33b92 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/media_type.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/media_type.py @@ -49,7 +49,7 @@ class MediaType(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py index 50fd627b5..7b3c93bfe 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flow.py @@ -35,7 +35,7 @@ class OAuthFlow(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py index 642a8c850..e23b3d5dd 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/oauth_flows.py @@ -35,4 +35,4 @@ class OAuthFlows(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow diff --git a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py index 3927cdd96..cbfaa2038 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/open_api.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/open_api.py @@ -60,4 +60,4 @@ class OpenAPI(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow diff --git a/openapi_python_client/schema/openapi_schema_pydantic/operation.py b/openapi_python_client/schema/openapi_schema_pydantic/operation.py index 620436214..98b2bd2ad 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/operation.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/operation.py @@ -104,7 +104,7 @@ class Operation(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py index 9e9d0674a..56070a720 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/parameter.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/parameter.py @@ -142,7 +142,7 @@ class Parameter(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py index 8c6bc12f8..cdbd6b564 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/path_item.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/path_item.py @@ -92,7 +92,7 @@ class PathItem(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/reference.py b/openapi_python_client/schema/openapi_schema_pydantic/reference.py index 54a73a290..851b28301 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/reference.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/reference.py @@ -16,7 +16,7 @@ class Reference(BaseModel): """**REQUIRED**. The reference string.""" class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [{"$ref": "#/components/schemas/Pet"}, {"$ref": "Pet.json"}, {"$ref": "definitions.json#/Pet"}] diff --git a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py index f15d41f31..3f7a602d9 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/request_body.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/request_body.py @@ -31,7 +31,7 @@ class RequestBody(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/response.py b/openapi_python_client/schema/openapi_schema_pydantic/response.py index 152661e7a..24899815e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/response.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/response.py @@ -44,7 +44,7 @@ class Response(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ { diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index 5a0cb1f91..029ef72c0 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -472,7 +472,7 @@ class Schema(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py index 868bb2c71..7bd3b4e8e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/security_scheme.py @@ -66,7 +66,7 @@ class SecurityScheme(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow allow_population_by_field_name = True schema_extra = { "examples": [ diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server.py b/openapi_python_client/schema/openapi_schema_pydantic/server.py index b94fe6bbd..949fec8c8 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server.py @@ -31,7 +31,7 @@ class Server(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ {"url": "https://development.gigantic-server.com/v1", "description": "Development server"}, diff --git a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py index 57254c2e1..8aa4fac46 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/server_variable.py @@ -28,4 +28,4 @@ class ServerVariable(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow diff --git a/openapi_python_client/schema/openapi_schema_pydantic/tag.py b/openapi_python_client/schema/openapi_schema_pydantic/tag.py index e80e57b93..1db40d325 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/tag.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/tag.py @@ -28,5 +28,5 @@ class Tag(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = {"examples": [{"name": "pet", "description": "Pets operations"}]} diff --git a/openapi_python_client/schema/openapi_schema_pydantic/xml.py b/openapi_python_client/schema/openapi_schema_pydantic/xml.py index 5feb02417..cfc9c64cb 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/xml.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/xml.py @@ -48,7 +48,7 @@ class XML(BaseModel): """ class Config: - extra = Extra.forbid + extra = Extra.allow schema_extra = { "examples": [ {"namespace": "http://example.com/schema/sample", "prefix": "sample"}, diff --git a/tests/test_parser/test_openapi.py b/tests/test_parser/test_openapi.py index b135543c4..a220d243c 100644 --- a/tests/test_parser/test_openapi.py +++ b/tests/test_parser/test_openapi.py @@ -92,15 +92,13 @@ def test_swagger_document_invalid_schema(self, mocker): header="Failed to parse OpenAPI document", detail=( "You may be trying to use a Swagger document; this is not supported by this project.\n\n" - "4 validation errors for OpenAPI\n" + "3 validation errors for OpenAPI\n" "info\n" " field required (type=value_error.missing)\n" "paths\n" " field required (type=value_error.missing)\n" "openapi\n" - " field required (type=value_error.missing)\n" - "swagger\n" - " extra fields not permitted (type=value_error.extra)" + " field required (type=value_error.missing)" ), ) Schemas.build.assert_not_called() From 59b6e5e640b215287404cb17aeda4baeee2eae85 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Thu, 13 May 2021 22:23:06 +0200 Subject: [PATCH 22/32] openapi_schema_pydantic / correct typing --- .../schema/openapi_schema_pydantic/callback.py | 7 +++++-- .../schema/openapi_schema_pydantic/encoding.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/callback.py b/openapi_python_client/schema/openapi_schema_pydantic/callback.py index 7607e95f3..9d95ffe89 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/callback.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/callback.py @@ -1,6 +1,9 @@ -from typing import Dict +from typing import TYPE_CHECKING, Dict -Callback = Dict[str, "PathItem"] # noqa +if TYPE_CHECKING: + from .path_item import PathItem + +Callback = Dict[str, "PathItem"] """ A map of possible out-of band callbacks related to the parent operation. Each value in the map is a [Path Item Object](#pathItemObject) diff --git a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py index ee0373469..a9c08a67e 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/encoding.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/encoding.py @@ -1,9 +1,12 @@ -from typing import Dict, Optional, Union +from typing import TYPE_CHECKING, Dict, Optional, Union from pydantic import BaseModel, Extra from .reference import Reference +if TYPE_CHECKING: + from .header import Header + class Encoding(BaseModel): """A single encoding definition applied to a single schema property.""" @@ -22,7 +25,7 @@ class Encoding(BaseModel): or a comma-separated list of the two types. """ - headers: Optional[Dict[str, Union["Header", Reference]]] = None # noqa + headers: Optional[Dict[str, Union["Header", Reference]]] = None """ A map allowing additional information to be provided as headers, for example `Content-Disposition`. From 9371b1b65cc8e8e34f3b6006a63352ddafa12d19 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Fri, 14 May 2021 00:52:09 +0200 Subject: [PATCH 23/32] openapi_schema_pydantic / schema / {any,all,one}Of default to `List.Empty` --- openapi_python_client/parser/properties/__init__.py | 8 ++------ openapi_python_client/parser/properties/model_property.py | 2 +- .../schema/openapi_schema_pydantic/schema.py | 6 +++--- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 1df7bea17..65dfe42f5 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -353,7 +353,7 @@ def build_union_property( ) -> Tuple[Union[UnionProperty, PropertyError], Schemas]: sub_properties: List[Property] = [] - for i, sub_prop_data in enumerate(chain(data.anyOf or [], data.oneOf or [])): + for i, sub_prop_data in enumerate(chain(data.anyOf, data.oneOf)): sub_prop, schemas = property_from_data( name=f"{name}_type{i}", required=required, @@ -440,11 +440,7 @@ def _property_from_data( if isinstance(data, oai.Reference): return _property_from_ref(name=name, required=required, parent=None, data=data, schemas=schemas) - sub_data: List[Union[oai.Schema, oai.Reference]] = [] - for _data in (data.allOf, data.anyOf, data.oneOf): - if _data: - sub_data.extend(_data) - + sub_data: List[Union[oai.Schema, oai.Reference]] = data.allOf + data.anyOf + data.oneOf # A union of a single reference should just be passed through to that reference (don't create copy class) if len(sub_data) == 1 and isinstance(sub_data[0], oai.Reference): return _property_from_ref(name=name, required=required, parent=data, data=sub_data[0], schemas=schemas) diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index a40460886..7ed00553d 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -88,7 +88,7 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]: return prop_or_error unprocessed_props = data.properties or {} - for sub_prop in data.allOf or []: + for sub_prop in data.allOf: if isinstance(sub_prop, oai.Reference): ref_path = parse_reference_path(sub_prop.ref) if isinstance(ref_path, ParseError): diff --git a/openapi_python_client/schema/openapi_schema_pydantic/schema.py b/openapi_python_client/schema/openapi_schema_pydantic/schema.py index 029ef72c0..87492eadc 100644 --- a/openapi_python_client/schema/openapi_schema_pydantic/schema.py +++ b/openapi_python_client/schema/openapi_schema_pydantic/schema.py @@ -224,7 +224,7 @@ class Schema(BaseModel): types defined by keyword. Recall: "number" includes "integer". """ - allOf: Optional[List[Union[Reference, "Schema"]]] = None + allOf: List[Union[Reference, "Schema"]] = Field(default_factory=list) """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** @@ -241,7 +241,7 @@ class Schema(BaseModel): value. """ - oneOf: Optional[List[Union[Reference, "Schema"]]] = None + oneOf: List[Union[Reference, "Schema"]] = Field(default_factory=list) """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** @@ -258,7 +258,7 @@ class Schema(BaseModel): keyword's value. """ - anyOf: Optional[List[Union[Reference, "Schema"]]] = None + anyOf: List[Union[Reference, "Schema"]] = Field(default_factory=list) """ **From OpenAPI spec: Inline or referenced schema MUST be of a [Schema Object](#schemaObject) and not a standard JSON Schema.** From 0d6606570e6855cd4f45f86f1775169c5a18fb0c Mon Sep 17 00:00:00 2001 From: p1-ra Date: Fri, 7 May 2021 23:43:34 +0200 Subject: [PATCH 24/32] parser / schemas / add indirect reference resolution --- .../parser/properties/__init__.py | 9 +++---- .../parser/properties/schemas.py | 27 +++++++++++++++++-- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 65dfe42f5..4ba1b2b70 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -22,7 +22,7 @@ from .enum_property import EnumProperty from .model_property import ModelProperty, build_model_property from .property import Property -from .schemas import Class, Schemas, parse_reference_path, update_schemas_with_data +from .schemas import Class, Schemas, parse_reference_path, update_schemas_with @attr.s(auto_attribs=True, frozen=True) @@ -559,18 +559,17 @@ def build_schemas( next_round = [] # Only accumulate errors from the last round, since we might fix some along the way for name, data in to_process: - if isinstance(data, oai.Reference): - schemas.errors.append(PropertyError(data=data, detail="Reference schemas are not supported.")) - continue ref_path = parse_reference_path(f"#/components/schemas/{name}") if isinstance(ref_path, ParseError): schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue - schemas_or_err = update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) + + schemas_or_err = update_schemas_with(ref_path=ref_path, data=data, schemas=schemas, config=config) if isinstance(schemas_or_err, PropertyError): next_round.append((name, data)) errors.append(schemas_or_err) continue + schemas = schemas_or_err still_making_progress = True to_process = next_round diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 71ecf3843..f3c3bd91e 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -1,4 +1,4 @@ -__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with_data"] +__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with"] from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast from urllib.parse import urlparse @@ -61,7 +61,30 @@ class Schemas: errors: List[ParseError] = attr.ib(factory=list) -def update_schemas_with_data( +def update_schemas_with( + *, ref_path: _ReferencePath, data: Union[oai.Reference, oai.Schema], schemas: Schemas, config: Config +) -> Union[Schemas, PropertyError]: + if isinstance(data, oai.Reference): + return _update_schemas_with_reference(ref_path=ref_path, data=data, schemas=schemas, config=config) + else: + return _update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) + + +def _update_schemas_with_reference( + *, ref_path: _ReferencePath, data: oai.Reference, schemas: Schemas, config: Config +) -> Union[Schemas, PropertyError]: + reference_pointer = parse_reference_path(data.ref) + if isinstance(reference_pointer, ParseError): + return PropertyError(detail=reference_pointer.detail, data=data) + + resolved_reference = schemas.classes_by_reference.get(reference_pointer) + if resolved_reference: + return attr.evolve(schemas, classes_by_reference={ref_path: resolved_reference, **schemas.classes_by_reference}) + else: + return PropertyError(f"Reference {ref_path} could not be resolved", data=data) + + +def _update_schemas_with_data( *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config ) -> Union[Schemas, PropertyError]: from . import property_from_data From 58fa3de11ed0277a9835f8fb99964e6e6a31e61d Mon Sep 17 00:00:00 2001 From: p1-ra Date: Sat, 8 May 2021 01:34:56 +0200 Subject: [PATCH 25/32] e2e / update `openapi.json` and golden-record --- .../my_test_api_client/models/__init__.py | 1 + ..._model_with_indirect_reference_property.py | 60 +++++++++++++++++++ end_to_end_tests/openapi.json | 15 +++++ 3 files changed, 76 insertions(+) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 0f7516048..57224ea7d 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -3,6 +3,7 @@ from .a_form_data import AFormData from .a_model import AModel from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject +from .a_model_with_indirect_reference_property import AModelWithIndirectReferenceProperty from .all_of_sub_model import AllOfSubModel from .an_all_of_enum import AnAllOfEnum from .an_enum import AnEnum diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py new file mode 100644 index 000000000..14058761d --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_reference_property.py @@ -0,0 +1,60 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..models.an_enum import AnEnum +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AModelWithIndirectReferenceProperty") + + +@attr.s(auto_attribs=True) +class AModelWithIndirectReferenceProperty: + """ """ + + an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + an_enum_indirect_ref: Union[Unset, str] = UNSET + if not isinstance(self.an_enum_indirect_ref, Unset): + an_enum_indirect_ref = self.an_enum_indirect_ref.value + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if an_enum_indirect_ref is not UNSET: + field_dict["an_enum_indirect_ref"] = an_enum_indirect_ref + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + an_enum_indirect_ref: Union[Unset, AnEnum] = UNSET + _an_enum_indirect_ref = d.pop("an_enum_indirect_ref", UNSET) + if not isinstance(_an_enum_indirect_ref, Unset): + an_enum_indirect_ref = AnEnum(_an_enum_indirect_ref) + + a_model_with_indirect_reference_property = cls( + an_enum_indirect_ref=an_enum_indirect_ref, + ) + + a_model_with_indirect_reference_property.additional_properties = d + return a_model_with_indirect_reference_property + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index bbd227358..db62033d5 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -1577,6 +1577,21 @@ "AByteStream": { "type": "string", "format": "byte" + }, + "AModelWithIndirectReferenceProperty": { + "title": "AModelWithIndirectReferenceProperty", + "type": "object", + "properties": { + "an_enum_indirect_ref": { + "$ref": "#/components/schemas/AnEnumDeeperIndirectReference" + } + } + }, + "AnEnumDeeperIndirectReference": { + "$ref": "#/components/schemas/AnEnumIndirectReference" + }, + "AnEnumIndirectReference": { + "$ref": "#/components/schemas/AnEnum" } } } From 4d67fe590397f873e491bcfc66c0df6c034b7a55 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Sat, 8 May 2021 03:32:15 +0200 Subject: [PATCH 26/32] parser / property / add recursive reference resoltion --- openapi_python_client/parser/errors.py | 9 +- .../parser/properties/.property.py.swp | Bin 0 -> 16384 bytes .../parser/properties/__init__.py | 94 ++++++++++++++++-- .../parser/properties/model_property.py | 12 ++- .../parser/properties/schemas.py | 51 +++++++--- 5 files changed, 142 insertions(+), 24 deletions(-) create mode 100644 openapi_python_client/parser/properties/.property.py.swp diff --git a/openapi_python_client/parser/errors.py b/openapi_python_client/parser/errors.py index 562e54428..6855ef6bd 100644 --- a/openapi_python_client/parser/errors.py +++ b/openapi_python_client/parser/errors.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from enum import Enum -from typing import Optional +from typing import Any, Optional __all__ = ["ErrorLevel", "GeneratorError", "ParseError", "PropertyError", "ValidationError"] @@ -39,5 +39,12 @@ class PropertyError(ParseError): header = "Problem creating a Property: " +@dataclass +class RecursiveReferenceInterupt(PropertyError): + """Error raised when a property have an recursive reference to itself""" + + schemas: Optional[Any] = None # TODO: shall not use Any here, shall be Schemas, to fix later + + class ValidationError(Exception): pass diff --git a/openapi_python_client/parser/properties/.property.py.swp b/openapi_python_client/parser/properties/.property.py.swp new file mode 100644 index 0000000000000000000000000000000000000000..b8aa28d437fb65f735ae9a452ad527368b3b38a0 GIT binary patch literal 16384 zcmeHNNslDO6%N3F&0-__#wn<=>?%~9GlL}ym)s-T>}Zx2-95H68f8aiMNJ2j6RRHx67aE=Kb5y~v2HteUO?`(#;>($`fL z%ZvBEw?t%i4v(FE_!Pf8ImU3^#@LU4^4#vTA6>)P4;a&OBDF0vbzSW`xv{x@W^--p z(XB_mvw8CP%ArpMfk#Jj|7bClT1?g2bY}Moy_V$)i-p#-ZcTmbRFp=RN2etwl~zd; zzRr^AY~^~fBQg*fXftpt`_^5@U8*1H+`+&0)tz={{2m#I3`7PZ1CfEqKx7~?5E+OJ z{GT&mr`NI{!p3X8otgi{699LG&+Gi}@HtxQ_dnxDWFRsS8Hfx-1|kEIfyh8)ATkgc zhzvvqA_M<}3<%uFe;ayzNH_ZU|9`mufA1@dy$k#S_&x9|;0@q);Ag-APypWpHh|;6 zU%t%P_kj;@W$Y#3Mc^6WIM4&S!0o_|z$dpb_GiEXr+^M{2k^m;on%W5B03GWK`iH^2+P zMPMDc8F=Rg#(oPt1N;)mfPY*M9)RBguK*et0@nZx_}dp4dkT06_%85u;3nW5JTUn+ z@FU;^@J-+wzzx8k@X+No;OD>?cmU`Ew*&uzKmG}jUp^q;{8tzEmm2et9E&QqTp6wl z%TE_t4j2mq=rfF1cb!d_%ne4;fN;$Rc6Ls0-M`&op-1LeiNqV3kCRe9Ua3-!R?T*U zys$ zzcel7SUqvs&Zg4%0o+m6=R4X++v%+oQA@x~Xwyv9Ft#=4v&n8Nb(qA!-=a|G0A1y| z80IbCdtrPX9VZLGfv%y*E`mz%FL_xY8VWFr?-!(&rLgco{aR1L+;IP==4!w< zc>RqeOe+h|jf+xpp=Z35xv*+q@@`&aBJc4@F{*OOwU|hg@a;u^(~`QH3}s0QBqZ;G zw9T3ulb- zx=Y!t7g(Y=gC418(?wdJ-`V$O4`@{vd0W!Du&&eR2=Yp4GnE-dJl~w{Xj!4)o?bgTXUgVDLH?*v48Qq^z`66Uj;5G9Aud_=xW+ z0}ZXvncU^sp3E*TB$qSV_b({6BG)J`yglR24u$?Xmo$BTsb$vX^X>ApT&fPAPsQ(Q zQVz>21R%UhW$ntuSkN@2)VbAKh8hXr)uL@4llTXYHF_&-BjeDdao7u zqJV*y>y*tQbCq zTA8FNYPzttOjB=s$G=-jyGEzaE30yoVCRQ#)hN7pszxY(YOG}0tp^w_F3L1RM!@3- zDNXM6YX6e0SR_oPuE4V&IWOu<=PqaJVRI9=Kx25Ck_jfA76vz}gE6Q`^&hx>?PNEmKI~q6m*h4i5hlEnRORC z4H5Nh+6w$rA` zZBLU5dP9yCIGNjnf=@(N7G^+gh=nS{$qbvX4LT(J-lk(o9p+YSEjr+*;lmI>VO>5} zI5u`s)N!or5oa*RmGA|A5`KzQgSE!u3`%znZh;NNz8Y3m1`pjQO@N_+$hqGMUo`p zjOwZrWB$ZSQ~emk5=DmZ2&JIkY;b*!116*U|GRPTz6;R(|8@R8lkWFl_U$S4#Vaxp z8Hfx-1|kEIfyh8)ATkgchzvvqA_I|ul?>ofL~}L&jJV_;`?VgbT^{hdZTWvTz005L SzZ?IFKs;Hy str: + self._ensure_resolved() + + prop = self.owner.data + assert isinstance(prop, Property) + return prop.get_base_type_string() + + def get_base_json_type_string(self) -> str: + self._ensure_resolved() + + prop = self.owner.data + assert isinstance(prop, Property) + return prop.get_base_json_type_string() + + def get_type_string(self, no_optional: bool = False, json: bool = False) -> str: + self._ensure_resolved() + + prop = self.owner.data + assert isinstance(prop, Property) + return prop.get_type_string(no_optional, json) + + def get_instance_type_string(self) -> str: + self._ensure_resolved() + return super().get_instance_type_string() + + def to_string(self) -> str: + self._ensure_resolved() + + if not self.required: + return f"{self.python_name}: Union[Unset, {self.get_type_string()}] = UNSET" + else: + return f"{self.python_name}: {self.get_type_string()}" + + def _ensure_resolved(self) -> None: + if self._resolved: + return + + if not isinstance(self.owner.data, Property): + raise RuntimeError(f"LazySelfReferenceProperty {self.name} owner shall have been resolved.") + else: + object.__setattr__(self, "_resolved", True) + object.__setattr__(self, "nullable", self.owner.data.nullable) + + @attr.s(auto_attribs=True, frozen=True) class StringProperty(Property): """A property of type str""" @@ -411,11 +464,18 @@ def _property_from_ref( ref_path = parse_reference_path(data.ref) if isinstance(ref_path, ParseError): return PropertyError(data=data, detail=ref_path.detail), schemas + existing = schemas.classes_by_reference.get(ref_path) - if not existing: + if not existing or not existing.data: return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas - prop = attr.evolve(existing, required=required, name=name) + if isinstance(existing.data, RecursiveReferenceInterupt): + return ( + LazySelfReferenceProperty(required=required, name=name, nullable=False, default=None, owner=existing), + schemas, + ) + + prop = attr.evolve(existing.data, required=required, name=name) if parent: prop = attr.evolve(prop, nullable=parent.nullable) if isinstance(prop, EnumProperty): @@ -551,12 +611,15 @@ def build_schemas( to_process: Iterable[Tuple[str, Union[oai.Reference, oai.Schema]]] = components.items() still_making_progress = True errors: List[PropertyError] = [] - + recursive_references_waiting_reprocess: Dict[str, Union[oai.Reference, oai.Schema]] = dict() + visited: Set[_ReferencePath] = set() + depth = 0 # References could have forward References so keep going as long as we are making progress while still_making_progress: still_making_progress = False errors = [] next_round = [] + # Only accumulate errors from the last round, since we might fix some along the way for name, data in to_process: ref_path = parse_reference_path(f"#/components/schemas/{name}") @@ -564,15 +627,28 @@ def build_schemas( schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue - schemas_or_err = update_schemas_with(ref_path=ref_path, data=data, schemas=schemas, config=config) + visited.add(ref_path) + schemas_or_err = update_schemas_with( + ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config + ) if isinstance(schemas_or_err, PropertyError): - next_round.append((name, data)) - errors.append(schemas_or_err) - continue + if isinstance(schemas_or_err, RecursiveReferenceInterupt): + up_schemas = schemas_or_err.schemas + assert isinstance(up_schemas, Schemas) # TODO fix typedef in RecursiveReferenceInterupt + schemas_or_err = up_schemas + recursive_references_waiting_reprocess[name] = data + else: + next_round.append((name, data)) + errors.append(schemas_or_err) + continue schemas = schemas_or_err still_making_progress = True + depth += 1 to_process = next_round + if len(recursive_references_waiting_reprocess.keys()): + schemas = build_schemas(components=recursive_references_waiting_reprocess, schemas=schemas, config=config) + schemas.errors.extend(errors) return schemas diff --git a/openapi_python_client/parser/properties/model_property.py b/openapi_python_client/parser/properties/model_property.py index 7ed00553d..70db0c5ed 100644 --- a/openapi_python_client/parser/properties/model_property.py +++ b/openapi_python_client/parser/properties/model_property.py @@ -11,6 +11,11 @@ from .schemas import Class, Schemas, parse_reference_path +@attr.s(auto_attribs=True, frozen=True) +class RecusiveProperty(Property): + pass + + @attr.s(auto_attribs=True, frozen=True) class ModelProperty(Property): """A property which refers to another Schema""" @@ -93,9 +98,12 @@ def _check_existing(prop: Property) -> Union[Property, PropertyError]: ref_path = parse_reference_path(sub_prop.ref) if isinstance(ref_path, ParseError): return PropertyError(detail=ref_path.detail, data=sub_prop) - sub_model = schemas.classes_by_reference.get(ref_path) - if sub_model is None: + + sub_model_ref = schemas.classes_by_reference.get(ref_path) + if sub_model_ref is None or not isinstance(sub_model_ref.data, Property): return PropertyError(f"Reference {sub_prop.ref} not found") + + sub_model = sub_model_ref.data if not isinstance(sub_model, ModelProperty): return PropertyError("Cannot take allOf a non-object") for prop in chain(sub_model.required_properties, sub_model.optional_properties): diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index f3c3bd91e..0dd782fde 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -1,6 +1,6 @@ -__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with"] +__all__ = ["Class", "Schemas", "parse_reference_path", "update_schemas_with", "_ReferencePath"] -from typing import TYPE_CHECKING, Dict, List, NewType, Union, cast +from typing import TYPE_CHECKING, Dict, Generic, List, NewType, Optional, Set, TypeVar, Union, cast from urllib.parse import urlparse import attr @@ -8,14 +8,14 @@ from ... import Config from ... import schema as oai from ... import utils -from ..errors import ParseError, PropertyError +from ..errors import ParseError, PropertyError, RecursiveReferenceInterupt if TYPE_CHECKING: # pragma: no cover from .property import Property else: Property = "Property" - +T = TypeVar("T") _ReferencePath = NewType("_ReferencePath", str) _ClassName = NewType("_ClassName", str) @@ -27,6 +27,11 @@ def parse_reference_path(ref_path_raw: str) -> Union[_ReferencePath, ParseError] return cast(_ReferencePath, parsed.fragment) +@attr.s(auto_attribs=True) +class _Holder(Generic[T]): + data: Optional[T] + + @attr.s(auto_attribs=True, frozen=True) class Class: """Represents Python class which will be generated from an OpenAPI schema""" @@ -56,22 +61,33 @@ def from_string(*, string: str, config: Config) -> "Class": class Schemas: """Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums)""" - classes_by_reference: Dict[_ReferencePath, Property] = attr.ib(factory=dict) - classes_by_name: Dict[_ClassName, Property] = attr.ib(factory=dict) + classes_by_reference: Dict[ + _ReferencePath, _Holder[Union[EnumProperty, ModelProperty, RecursiveReferenceInterupt]] + ] = attr.ib(factory=dict) + classes_by_name: Dict[ + _ClassName, _Holder[Union[EnumProperty, ModelProperty, RecursiveReferenceInterupt]] + ] = attr.ib(factory=dict) errors: List[ParseError] = attr.ib(factory=list) def update_schemas_with( - *, ref_path: _ReferencePath, data: Union[oai.Reference, oai.Schema], schemas: Schemas, config: Config + *, + ref_path: _ReferencePath, + data: Union[oai.Reference, oai.Schema], + schemas: Schemas, + visited: Set[_ReferencePath], + config: Config, ) -> Union[Schemas, PropertyError]: if isinstance(data, oai.Reference): - return _update_schemas_with_reference(ref_path=ref_path, data=data, schemas=schemas, config=config) + return _update_schemas_with_reference( + ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config + ) else: - return _update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, config=config) + return _update_schemas_with_data(ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config) def _update_schemas_with_reference( - *, ref_path: _ReferencePath, data: oai.Reference, schemas: Schemas, config: Config + *, ref_path: _ReferencePath, data: oai.Reference, schemas: Schemas, visited: Set[_ReferencePath], config: Config ) -> Union[Schemas, PropertyError]: reference_pointer = parse_reference_path(data.ref) if isinstance(reference_pointer, ParseError): @@ -85,7 +101,7 @@ def _update_schemas_with_reference( def _update_schemas_with_data( - *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, config: Config + *, ref_path: _ReferencePath, data: oai.Schema, schemas: Schemas, visited: Set[_ReferencePath], config: Config ) -> Union[Schemas, PropertyError]: from . import property_from_data @@ -94,8 +110,19 @@ def _update_schemas_with_data( data=data, name=ref_path, schemas=schemas, required=True, parent_name="", config=config ) + + holder = schemas.classes_by_reference.get(ref_path) if isinstance(prop, PropertyError): + if ref_path in visited and not holder: + holder = _Holder(data=RecursiveReferenceInterupt()) + schemas = attr.evolve(schemas, classes_by_reference={ref_path: holder, **schemas.classes_by_reference}) + return RecursiveReferenceInterupt(schemas=schemas) return prop - schemas = attr.evolve(schemas, classes_by_reference={ref_path: prop, **schemas.classes_by_reference}) + if holder: + holder.data = prop + else: + schemas = attr.evolve( + schemas, classes_by_reference={ref_path: _Holder(data=prop), **schemas.classes_by_reference} + ) return schemas From f4d7ff045725a9224ce5580c876bee13d7f4e186 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Sat, 8 May 2021 15:38:20 +0200 Subject: [PATCH 27/32] e2e / update `openapi.json` and golden-record --- .../my_test_api_client/models/__init__.py | 1 + ...l_with_indirect_self_reference_property.py | 77 +++++++++++++++++++ end_to_end_tests/openapi.json | 23 ++++++ 3 files changed, 101 insertions(+) create mode 100644 end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 57224ea7d..8712084ff 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -4,6 +4,7 @@ from .a_model import AModel from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject from .a_model_with_indirect_reference_property import AModelWithIndirectReferenceProperty +from .a_model_with_indirect_self_reference_property import AModelWithIndirectSelfReferenceProperty from .all_of_sub_model import AllOfSubModel from .an_all_of_enum import AnAllOfEnum from .an_enum import AnEnum diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py new file mode 100644 index 000000000..2c101f264 --- /dev/null +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_indirect_self_reference_property.py @@ -0,0 +1,77 @@ +from typing import Any, Dict, List, Type, TypeVar, Union + +import attr + +from ..models.an_enum import AnEnum +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AModelWithIndirectSelfReferenceProperty") + + +@attr.s(auto_attribs=True) +class AModelWithIndirectSelfReferenceProperty: + """ """ + + required_self_ref: AModelWithIndirectSelfReferenceProperty + an_enum: Union[Unset, AnEnum] = UNSET + optional_self_ref: Union[Unset, AModelWithIndirectSelfReferenceProperty] = UNSET + additional_properties: Dict[str, Any] = attr.ib(init=False, factory=dict) + + def to_dict(self) -> Dict[str, Any]: + required_self_ref = self.required_self_ref + an_enum: Union[Unset, str] = UNSET + if not isinstance(self.an_enum, Unset): + an_enum = self.an_enum.value + + optional_self_ref = self.optional_self_ref + + field_dict: Dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "required_self_ref": required_self_ref, + } + ) + if an_enum is not UNSET: + field_dict["an_enum"] = an_enum + if optional_self_ref is not UNSET: + field_dict["optional_self_ref"] = optional_self_ref + + return field_dict + + @classmethod + def from_dict(cls: Type[T], src_dict: Dict[str, Any]) -> T: + d = src_dict.copy() + required_self_ref = d.pop("required_self_ref") + + an_enum: Union[Unset, AnEnum] = UNSET + _an_enum = d.pop("an_enum", UNSET) + if not isinstance(_an_enum, Unset): + an_enum = AnEnum(_an_enum) + + optional_self_ref = d.pop("optional_self_ref", UNSET) + + a_model_with_indirect_self_reference_property = cls( + required_self_ref=required_self_ref, + an_enum=an_enum, + optional_self_ref=optional_self_ref, + ) + + a_model_with_indirect_self_reference_property.additional_properties = d + return a_model_with_indirect_self_reference_property + + @property + def additional_keys(self) -> List[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index db62033d5..246201178 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -1592,6 +1592,29 @@ }, "AnEnumIndirectReference": { "$ref": "#/components/schemas/AnEnum" + }, + "AModelWithIndirectSelfReferenceProperty": { + "type": "object", + "properties": { + "an_enum": { + "$ref": "#/components/schemas/AnEnum" + }, + "required_self_ref": { + "$ref": "#/components/schemas/AnDeeperIndirectSelfReference" + }, + "optional_self_ref": { + "$ref": "#/components/schemas/AnDeeperIndirectSelfReference" + } + }, + "required": [ + "required_self_ref" + ] + }, + "AnDeeperIndirectSelfReference": { + "$ref": "#/components/schemas/AnIndirectSelfReference" + }, + "AnIndirectSelfReference": { + "$ref": "#/components/schemas/AModelWithIndirectSelfReferenceProperty" } } } From 3667232f57d595897503c026ff57d5497dc86b50 Mon Sep 17 00:00:00 2001 From: Nementon Date: Fri, 7 May 2021 19:46:07 +0200 Subject: [PATCH 28/32] parser / properties / do not restrict reference pointer type to `enum` or `object` --- openapi_python_client/parser/properties/schemas.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openapi_python_client/parser/properties/schemas.py b/openapi_python_client/parser/properties/schemas.py index 0dd782fde..6e369e6ab 100644 --- a/openapi_python_client/parser/properties/schemas.py +++ b/openapi_python_client/parser/properties/schemas.py @@ -62,10 +62,10 @@ class Schemas: """Structure for containing all defined, shareable, and reusable schemas (attr classes and Enums)""" classes_by_reference: Dict[ - _ReferencePath, _Holder[Union[EnumProperty, ModelProperty, RecursiveReferenceInterupt]] + _ReferencePath, _Holder[Union[Property, RecursiveReferenceInterupt]] ] = attr.ib(factory=dict) classes_by_name: Dict[ - _ClassName, _Holder[Union[EnumProperty, ModelProperty, RecursiveReferenceInterupt]] + _ClassName, _Holder[Union[Property, RecursiveReferenceInterupt]] ] = attr.ib(factory=dict) errors: List[ParseError] = attr.ib(factory=list) @@ -110,7 +110,6 @@ def _update_schemas_with_data( data=data, name=ref_path, schemas=schemas, required=True, parent_name="", config=config ) - holder = schemas.classes_by_reference.get(ref_path) if isinstance(prop, PropertyError): if ref_path in visited and not holder: From f46d1e631053c86b18848275d0924ec823f6d984 Mon Sep 17 00:00:00 2001 From: Nementon Date: Fri, 7 May 2021 20:53:32 +0200 Subject: [PATCH 29/32] e2e / update `openapi.json` and golden-record --- .../my_test_api_client/models/__init__.py | 1 + end_to_end_tests/openapi.json | 261 ++++++++++++++++++ 2 files changed, 262 insertions(+) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py index 8712084ff..0efd7dcb6 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/__init__.py @@ -5,6 +5,7 @@ from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject from .a_model_with_indirect_reference_property import AModelWithIndirectReferenceProperty from .a_model_with_indirect_self_reference_property import AModelWithIndirectSelfReferenceProperty +from .a_model_with_properties_reference_that_are_not_object import AModelWithPropertiesReferenceThatAreNotObject from .all_of_sub_model import AllOfSubModel from .an_all_of_enum import AnAllOfEnum from .an_enum import AnEnum diff --git a/end_to_end_tests/openapi.json b/end_to_end_tests/openapi.json index 246201178..094bd920a 100644 --- a/end_to_end_tests/openapi.json +++ b/end_to_end_tests/openapi.json @@ -1615,6 +1615,267 @@ }, "AnIndirectSelfReference": { "$ref": "#/components/schemas/AModelWithIndirectSelfReferenceProperty" + }, + "AModelWithPropertiesReferenceThatAreNotObject": { + "type": "object", + "properties": { + "enum_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfEnum" + }, + "str_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfString" + }, + "date_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDate" + }, + "datetime_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDateTime" + }, + "int32_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt32" + }, + "int64_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfInt64" + }, + "float_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFloat" + }, + "double_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfDouble" + }, + "file_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfFile" + }, + "bytestream_properties_ref": { + "$ref": "#/components/schemas/AnOtherArrayOfByteStream" + }, + "enum_properties": { + "$ref": "#/components/schemas/AnArrayOfEnum" + }, + "str_properties": { + "$ref": "#/components/schemas/AnArrayOfString" + }, + "date_properties": { + "$ref": "#/components/schemas/AnArrayOfDate" + }, + "datetime_properties": { + "$ref": "#/components/schemas/AnArrayOfDateTime" + }, + "int32_properties": { + "$ref": "#/components/schemas/AnArrayOfInt32" + }, + "int64_properties": { + "$ref": "#/components/schemas/AnArrayOfInt64" + }, + "float_properties": { + "$ref": "#/components/schemas/AnArrayOfFloat" + }, + "double_properties": { + "$ref": "#/components/schemas/AnArrayOfDouble" + }, + "file_properties": { + "$ref": "#/components/schemas/AnArrayOfFile" + }, + "bytestream_properties": { + "$ref": "#/components/schemas/AnArrayOfByteStream" + }, + "enum_property_ref": { + "$ref": "#/components/schemas/AnEnum" + }, + "str_property_ref": { + "$ref": "#/components/schemas/AString" + }, + "date_property_ref": { + "$ref": "#/components/schemas/ADate" + }, + "datetime_property_ref": { + "$ref": "#/components/schemas/ADateTime" + }, + "int32_property_ref": { + "$ref": "#/components/schemas/AnInt32" + }, + "int64_property_ref": { + "$ref": "#/components/schemas/AnInt64" + }, + "float_property_ref": { + "$ref": "#/components/schemas/AFloat" + }, + "double_property_ref": { + "$ref": "#/components/schemas/ADouble" + }, + "file_property_ref": { + "$ref": "#/components/schemas/AFile" + }, + "bytestream_property_ref": { + "$ref": "#/components/schemas/AByteStream" + } + } + }, + "AnArrayOfEnum": { + "type": "array", + "items": { + "title": "AnEnum", + "enum": ["FIRST_VALUE", "SECOND_VALUE"], + "description": "For testing Enums in all the ways they can be used " + } + }, + "AnOtherArrayOfEnum": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnEnum" + } + }, + "AnArrayOfString": { + "type": "array", + "items": { + "type": "string" + } + }, + "AnOtherArrayOfString": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AString" + } + }, + "AString": { + "type": "string", + "pattern": "^helloworld.*" + }, + "AnArrayOfDate": { + "type": "array", + "items": { + "type": "string", + "format": "date" + } + }, + "AnOtherArrayOfDate": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADate" + } + }, + "ADate": { + "type": "string", + "format": "date" + }, + "AnArrayOfDateTime": { + "type": "array", + "items": { + "type": "string", + "format": "date-time" + } + }, + "AnOtherArrayOfDateTime": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADateTime" + } + }, + "ADateTime": { + "type": "string", + "format": "date-time" + }, + "AnArrayOfInt32": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + }, + "AnOtherArrayOfInt32": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt32" + } + }, + "AnInt32": { + "type": "integer", + "format": "int32" + }, + "AnArrayOfInt64": { + "type": "array", + "items": { + "type": "integer", + "format": "int64" + } + }, + "AnOtherArrayOfInt64": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AnInt64" + } + }, + "AnInt64": { + "type": "integer", + "format": "int64" + }, + "AnArrayOfFloat": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfFloat": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFloat" + } + }, + "AFloat": { + "type": "number", + "format": "float" + }, + "AnArrayOfDouble": { + "type": "array", + "items": { + "type": "number", + "format": "float" + } + }, + "AnOtherArrayOfDouble": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ADouble" + } + }, + "ADouble": { + "type": "number", + "format": "double" + }, + "AnArrayOfFile": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + }, + "AnOtherArrayOfFile": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AFile" + } + }, + "AFile": { + "type": "string", + "format": "binary" + }, + "AnArrayOfByteStream": { + "type": "array", + "items": { + "type": "string", + "format": "byte" + } + }, + "AnOtherArrayOfByteStream": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AByteStream" + } + }, + "AByteStream": { + "type": "string", + "format": "byte" } } } From 6d4416d6a26a90259fa96e1659b10b7f0d227e8b Mon Sep 17 00:00:00 2001 From: Nementon Date: Fri, 7 May 2021 21:16:55 +0200 Subject: [PATCH 30/32] tpl / model / add missing typing import: `Tuple,Optional,BinaryIO,TextIO` --- .../a_model_with_properties_reference_that_are_not_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py index 797b57b38..4b95ec80e 100644 --- a/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py +++ b/end_to_end_tests/golden-record/my_test_api_client/models/a_model_with_properties_reference_that_are_not_object.py @@ -1,6 +1,6 @@ import datetime from io import BytesIO -from typing import Any, Dict, List, Type, TypeVar, cast +from typing import Any, BinaryIO, Dict, List, Optional, TextIO, Tuple, Type, TypeVar, Union, cast import attr from dateutil.parser import isoparse From c8abe920f4d9a5620a176c526c938547bdfc9991 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Tue, 18 May 2021 17:03:53 +0200 Subject: [PATCH 31/32] parser / property / correct recursive ref detection logic --- openapi_python_client/parser/properties/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 5402ed34c..3069f7823 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -614,6 +614,7 @@ def build_schemas( recursive_references_waiting_reprocess: Dict[str, Union[oai.Reference, oai.Schema]] = dict() visited: Set[_ReferencePath] = set() depth = 0 + # References could have forward References so keep going as long as we are making progress while still_making_progress: still_making_progress = False @@ -627,11 +628,12 @@ def build_schemas( schemas.errors.append(PropertyError(detail=ref_path.detail, data=data)) continue - visited.add(ref_path) schemas_or_err = update_schemas_with( ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config ) if isinstance(schemas_or_err, PropertyError): + visited.add(ref_path) + if isinstance(schemas_or_err, RecursiveReferenceInterupt): up_schemas = schemas_or_err.schemas assert isinstance(up_schemas, Schemas) # TODO fix typedef in RecursiveReferenceInterupt From c396e4ea349f434b77770e4bbe0123f530235ad3 Mon Sep 17 00:00:00 2001 From: p1-ra Date: Wed, 19 May 2021 17:55:24 +0200 Subject: [PATCH 32/32] disabled recursive recursion detection, does not properly works --- openapi_python_client/parser/properties/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openapi_python_client/parser/properties/__init__.py b/openapi_python_client/parser/properties/__init__.py index 3069f7823..539559d17 100644 --- a/openapi_python_client/parser/properties/__init__.py +++ b/openapi_python_client/parser/properties/__init__.py @@ -632,7 +632,8 @@ def build_schemas( ref_path=ref_path, data=data, schemas=schemas, visited=visited, config=config ) if isinstance(schemas_or_err, PropertyError): - visited.add(ref_path) + ### TODO: Recursive reference detection does not properly works, disabled for now + # visited.add(ref_path) if isinstance(schemas_or_err, RecursiveReferenceInterupt): up_schemas = schemas_or_err.schemas