Import upstream version 5.2.2
Kali Janitor
2 years ago
0 | name: build | |
1 | on: | |
2 | push: | |
3 | branches: ["dev"] | |
4 | tags: ["*"] | |
5 | pull_request: | |
6 | # Run builds nightly to catch incompatibilities with new marshmallow releases | |
7 | schedule: | |
8 | - cron: "0 0 * * *" | |
9 | jobs: | |
10 | lint: | |
11 | name: lint | |
12 | runs-on: ubuntu-latest | |
13 | steps: | |
14 | - uses: actions/checkout@v2 | |
15 | - uses: actions/setup-python@v2 | |
16 | - run: python -m pip install --upgrade pip wheel | |
17 | - run: pip install tox | |
18 | - run: tox -elint | |
19 | tests: | |
20 | name: ${{ matrix.name }} | |
21 | runs-on: ${{ matrix.os }} | |
22 | strategy: | |
23 | fail-fast: false | |
24 | matrix: | |
25 | include: | |
26 | - {name: '3.7-ma3', python: '3.7', os: ubuntu-latest, tox: py37-marshmallow3} | |
27 | - {name: '3.10-ma3', python: '3.10', os: ubuntu-latest, tox: py310-marshmallow3} | |
28 | - {name: '3.10-madev', python: '3.10', os: ubuntu-latest, tox: py310-marshmallowdev} | |
29 | steps: | |
30 | - uses: actions/checkout@v2 | |
31 | - uses: actions/setup-python@v2 | |
32 | with: | |
33 | python-version: ${{ matrix.python }} | |
34 | - run: python -m pip install --upgrade pip wheel | |
35 | - run: pip install tox | |
36 | - run: tox -e${{ matrix.tox }} | |
37 | release: | |
38 | needs: [lint, tests] | |
39 | name: PyPI release | |
40 | if: startsWith(github.ref, 'refs/tags') | |
41 | runs-on: ubuntu-latest | |
42 | steps: | |
43 | - uses: actions/checkout@v2 | |
44 | - uses: actions/setup-python@v2 | |
45 | - name: install requirements | |
46 | run: python -m pip install build twine | |
47 | - name: build dists | |
48 | run: python -m build | |
49 | - name: check package metadata | |
50 | run: twine check dist/* | |
51 | - name: publish | |
52 | run: twine upload -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} dist/* |
0 | 0 | repos: |
1 | 1 | - repo: https://github.com/asottile/pyupgrade |
2 | rev: v2.11.0 | |
2 | rev: v2.32.0 | |
3 | 3 | hooks: |
4 | 4 | - id: pyupgrade |
5 | args: [--py36-plus] | |
5 | args: [--py37-plus] | |
6 | 6 | - repo: https://github.com/python/black |
7 | rev: 20.8b1 | |
7 | rev: 22.3.0 | |
8 | 8 | hooks: |
9 | 9 | - id: black |
10 | 10 | language_version: python3 |
11 | - repo: https://gitlab.com/pycqa/flake8 | |
12 | rev: 3.9.0 | |
11 | - repo: https://github.com/pycqa/flake8 | |
12 | rev: 4.0.1 | |
13 | 13 | hooks: |
14 | 14 | - id: flake8 |
15 | additional_dependencies: [flake8-bugbear==21.4.3] | |
15 | additional_dependencies: [flake8-bugbear==22.4.25] | |
16 | 16 | - repo: https://github.com/asottile/blacken-docs |
17 | rev: v1.10.0 | |
17 | rev: v1.12.1 | |
18 | 18 | hooks: |
19 | 19 | - id: blacken-docs |
20 | additional_dependencies: [black==20.8b1] | |
20 | additional_dependencies: [black==22.3.0] | |
21 | - repo: https://github.com/pre-commit/mirrors-mypy | |
22 | rev: v0.950 | |
23 | hooks: | |
24 | - id: mypy | |
25 | additional_dependencies: ["marshmallow>=3,<4", "types-PyYAML"] |
72 | 72 | - Luke Whitehorn `<https://github.com/Anti-Distinctlyminty>`_ |
73 | 73 | - François Magimel `<https://github.com/Linkid>`_ |
74 | 74 | - Stefan van der Walt `<https://github.com/stefanv>`_ |
75 | - `<https://github.com/kasium>`_ | |
76 | - Edwin Erdmanis `@vorticity <https://github.com/vorticity>`_ |
0 | 0 | Changelog |
1 | 1 | --------- |
2 | 2 | |
3 | 5.2.2 (2022-05-13) | |
4 | ****************** | |
5 | ||
6 | Bug fixes: | |
7 | ||
8 | - Fix schema property ordering regression in ``ApiSpec.to_yaml()`` (:issue:`768`). | |
9 | Thanks :user:`vorticity` for the PR. | |
10 | ||
11 | 5.2.1 (2022-05-01) | |
12 | ****************** | |
13 | ||
14 | Bug fixes: | |
15 | ||
16 | - Fix type hints for ``APISpec.path`` and ``BasePlugin`` methods (:pr:`765`). | |
17 | ||
18 | 5.2.0 (2022-04-29) | |
19 | ****************** | |
20 | ||
21 | Features: | |
22 | ||
23 | - Use ``raise from`` whenever possible (:pr:`763`). | |
24 | ||
25 | Refactoring: | |
26 | ||
27 | - Use a ``tuple`` rather than a ``namedtuple`` for "schema key" (:pr:`725`). | |
28 | ||
29 | Other changes: | |
30 | ||
31 | - Add type hints (:pr:`747`). Thanks :user:`kasium` for the PR. | |
32 | - Test against Python 3.10 (:pr:`724`). | |
33 | - Drop support for Python 3.6 (:pr:`727`). | |
34 | - Switch to Github Actions for CI (:pr:`751`). | |
35 | ||
3 | 36 | 5.1.1 (2021-09-27) |
4 | 37 | ****************** |
5 | 38 | |
9 | 42 | |
10 | 43 | Other changes: |
11 | 44 | |
12 | * Don't build universal wheels. We don't support Python 2 anymore. | |
45 | - Don't build universal wheels. We don't support Python 2 anymore. | |
13 | 46 | (:pr:`705`) |
14 | * Make the build reproducible (:pr:`#669`). | |
47 | - Make the build reproducible (:pr:`669`). | |
15 | 48 | |
16 | 49 | 5.1.0 (2021-08-10) |
17 | 50 | ****************** |
767 | 800 | |
768 | 801 | Bug fixes: |
769 | 802 | |
770 | * [apispec.ext.flask]: Don't document view methods that aren't included | |
803 | - [apispec.ext.flask]: Don't document view methods that aren't included | |
771 | 804 | in ``app.add_url_rule(..., methods=[...]))`` (:issue:`173`). Thanks :user:`ukaratay`. |
772 | 805 | |
773 | 806 | 0.27.0 (2017-10-30) |
775 | 808 | |
776 | 809 | Features: |
777 | 810 | |
778 | * [apispec.core]: Add ``register_operation_helper``. | |
779 | ||
780 | Bug fixes: | |
781 | ||
782 | * Order of plugins does not matter (:issue:`136`). | |
811 | - [apispec.core]: Add ``register_operation_helper``. | |
812 | ||
813 | Bug fixes: | |
814 | ||
815 | - Order of plugins does not matter (:issue:`136`). | |
783 | 816 | |
784 | 817 | Thanks :user:`yoichi` for these changes. |
785 | 818 | |
788 | 821 | |
789 | 822 | Features: |
790 | 823 | |
791 | * [apispec.ext.marshmallow]: Generate "enum" property with single entry | |
824 | - [apispec.ext.marshmallow]: Generate "enum" property with single entry | |
792 | 825 | when the ``validate.Equal`` validator is used (:issue:`155`). Thanks |
793 | 826 | :user:`Bangertm` for the suggestion and PR. |
794 | 827 | |
795 | 828 | Bug fixes: |
796 | 829 | |
797 | * Allow OPTIONS to be documented (:issue:`162`). Thanks :user:`buxx` for | |
830 | - Allow OPTIONS to be documented (:issue:`162`). Thanks :user:`buxx` for | |
798 | 831 | the PR. |
799 | * Fix regression from 0.25.3 that caused a ``KeyError`` (:issue:`163`). Thanks | |
832 | - Fix regression from 0.25.3 that caused a ``KeyError`` (:issue:`163`). Thanks | |
800 | 833 | :user:`yoichi`. |
801 | 834 | |
802 | 835 | 0.25.4 (2017-10-09) |
804 | 837 | |
805 | 838 | Bug fixes: |
806 | 839 | |
807 | * [apispec.ext.marshmallow]: Fix swagger location mapping for ``default_in`` | |
840 | - [apispec.ext.marshmallow]: Fix swagger location mapping for ``default_in`` | |
808 | 841 | param in fields2parameters (:issue:`156`). Thanks :user:`decaz`. |
809 | 842 | |
810 | 843 | 0.25.3 (2017-09-27) |
812 | 845 | |
813 | 846 | Bug fixes: |
814 | 847 | |
815 | * [apispec.ext.marshmallow]: Correctly handle multiple fields with | |
848 | - [apispec.ext.marshmallow]: Correctly handle multiple fields with | |
816 | 849 | ``location=json`` (:issue:`75`). Thanks :user:`shaicantor` for |
817 | 850 | reporting and thanks :user:`yoichi` for the patch. |
818 | 851 | |
822 | 855 | |
823 | 856 | Bug fixes: |
824 | 857 | |
825 | * [apispec.ext.marshmallow]: Avoid AttributeError when passing non-dict | |
858 | - [apispec.ext.marshmallow]: Avoid AttributeError when passing non-dict | |
826 | 859 | items to path objects (:issue:`151`). Thanks :user:`yoichi`. |
827 | 860 | |
828 | 861 | 0.25.1 (2017-08-23) |
830 | 863 | |
831 | 864 | Bug fixes: |
832 | 865 | |
833 | * [apispec.ext.marshmallow]: Fix ``use_instances`` when ``many=True`` is | |
866 | - [apispec.ext.marshmallow]: Fix ``use_instances`` when ``many=True`` is | |
834 | 867 | set (:issue:`148`). Thanks :user:`theirix`. |
835 | 868 | |
836 | 869 | 0.25.0 (2017-08-15) |
838 | 871 | |
839 | 872 | Features: |
840 | 873 | |
841 | * [apispec.ext.marshmallow]: Add ``use_instances`` parameter to | |
874 | - [apispec.ext.marshmallow]: Add ``use_instances`` parameter to | |
842 | 875 | ``fields2paramters`` (:issue:`144`). Thanks :user:`theirix`. |
843 | 876 | |
844 | 877 | Other changes: |
845 | 878 | |
846 | * Don't swallow ``YAMLError`` when YAML parsing fails | |
879 | - Don't swallow ``YAMLError`` when YAML parsing fails | |
847 | 880 | (:issue:`135`). Thanks :user:`djanderson` for the suggestion |
848 | 881 | and the PR. |
849 | 882 | |
852 | 885 | |
853 | 886 | Features: |
854 | 887 | |
855 | * [apispec.ext.marshmallow]: Add ``swagger.map_to_swagger_field`` | |
888 | - [apispec.ext.marshmallow]: Add ``swagger.map_to_swagger_field`` | |
856 | 889 | decorator to support custom field classes (:issue:`120`). Thanks |
857 | 890 | :user:`frol` for the suggestion and thanks :user:`dradetsky` for the |
858 | 891 | PR. |
862 | 895 | |
863 | 896 | Bug fixes: |
864 | 897 | |
865 | * [apispec.ext.marshmallow]: Fix swagger location mapping for | |
898 | - [apispec.ext.marshmallow]: Fix swagger location mapping for | |
866 | 899 | ``default_in`` param in `property2parameter` (:issue:`142`). Thanks |
867 | 900 | :user:`decaz`. |
868 | 901 | |
869 | 902 | 0.23.0 (2017-08-03) |
870 | 903 | +++++++++++++++++++ |
871 | 904 | |
872 | * Pass `operations` constructed by plugins to downstream marshmallow | |
905 | - Pass `operations` constructed by plugins to downstream marshmallow | |
873 | 906 | plugin (:issue:`138`). Thanks :user:`yoichi`. |
874 | * [apispec.ext.marshmallow] Generate parameter specification from marshmallow Schemas (:issue:`127`). | |
907 | - [apispec.ext.marshmallow] Generate parameter specification from marshmallow Schemas (:issue:`127`). | |
875 | 908 | Thanks :user:`ewalker11` for the suggestion thanks :user:`yoichi` for the PR. |
876 | * [apispec.ext.flask] Add support for Flask MethodViews (:issue:`85`, | |
909 | - [apispec.ext.flask] Add support for Flask MethodViews (:issue:`85`, | |
877 | 910 | :issue:`125`). Thanks :user:`lafrech` and :user:`boosh` for the |
878 | 911 | suggestion. Thanks :user:`djanderson` and :user:`yoichi` for the PRs. |
879 | 912 | |
880 | 913 | 0.22.3 (2017-07-16) |
881 | 914 | +++++++++++++++++++ |
882 | 915 | |
883 | * Release wheel distribution. | |
916 | - Release wheel distribution. | |
884 | 917 | |
885 | 918 | 0.22.2 (2017-07-12) |
886 | 919 | +++++++++++++++++++ |
887 | 920 | |
888 | 921 | Bug fixes: |
889 | 922 | |
890 | * [apispec.ext.marshmallow]: Properly handle callable ``default`` values | |
923 | - [apispec.ext.marshmallow]: Properly handle callable ``default`` values | |
891 | 924 | in output spec (:issue:`131`). Thanks :user:`NightBlues`. |
892 | 925 | |
893 | 926 | 0.22.1 (2017-06-25) |
895 | 928 | |
896 | 929 | Bug fixes: |
897 | 930 | |
898 | * [apispec.ext.marshmallow]: Include ``default`` in output spec when | |
931 | - [apispec.ext.marshmallow]: Include ``default`` in output spec when | |
899 | 932 | ``False`` is the default for a ``Boolean`` field (:issue:`130`). |
900 | 933 | Thanks :user:`nebularazer`. |
901 | 934 | |
904 | 937 | |
905 | 938 | Features: |
906 | 939 | |
907 | * [apispec.ext.bottle] Added bottle plugin (:issue:`128`). Thanks :user:`lucasrc`. | |
940 | - [apispec.ext.bottle] Added bottle plugin (:issue:`128`). Thanks :user:`lucasrc`. | |
908 | 941 | |
909 | 942 | 0.21.0 (2017-04-21) |
910 | 943 | +++++++++++++++++++ |
911 | 944 | |
912 | 945 | Features: |
913 | 946 | |
914 | * [apispec.ext.marshmallow] Sort list of required field names in generated spec (:issue:`124`). Thanks :user:`dradetsky`. | |
947 | - [apispec.ext.marshmallow] Sort list of required field names in generated spec (:issue:`124`). Thanks :user:`dradetsky`. | |
915 | 948 | |
916 | 949 | 0.20.1 (2017-04-18) |
917 | 950 | +++++++++++++++++++ |
918 | 951 | |
919 | 952 | Bug fixes: |
920 | 953 | |
921 | * [apispec.ext.tornado]: Fix compatibility with Tornado>=4.5. | |
922 | * [apispec.ext.tornado]: Fix adding paths for handlers with coroutine methods in Python 2 (:issue:`99`). | |
954 | - [apispec.ext.tornado]: Fix compatibility with Tornado>=4.5. | |
955 | - [apispec.ext.tornado]: Fix adding paths for handlers with coroutine methods in Python 2 (:issue:`99`). | |
923 | 956 | |
924 | 957 | 0.20.0 (2017-03-19) |
925 | 958 | +++++++++++++++++++ |
926 | 959 | |
927 | 960 | Features: |
928 | 961 | |
929 | * [apispec.core]: Definition helper functions receive the ``definition`` keyword argument, which is the current state of the definition (:issue:`122`). Thanks :user:`martinlatrille` for the PR. | |
930 | ||
931 | Other changes: | |
932 | ||
933 | * [apispec.ext.marshmallow] *Backwards-incompatible*: Remove ``dump`` parameter from ``schema2parameters``, ``fields2parameters``, and ``field2parameter`` (:issue:`114`). Thanks :user:`lafrech` and :user:`frol` for the feedback and :user:`lafrech` for the PR. | |
962 | - [apispec.core]: Definition helper functions receive the ``definition`` keyword argument, which is the current state of the definition (:issue:`122`). Thanks :user:`martinlatrille` for the PR. | |
963 | ||
964 | Other changes: | |
965 | ||
966 | - [apispec.ext.marshmallow] *Backwards-incompatible*: Remove ``dump`` parameter from ``schema2parameters``, ``fields2parameters``, and ``field2parameter`` (:issue:`114`). Thanks :user:`lafrech` and :user:`frol` for the feedback and :user:`lafrech` for the PR. | |
934 | 967 | |
935 | 968 | 0.19.0 (2017-03-05) |
936 | 969 | +++++++++++++++++++ |
937 | 970 | |
938 | 971 | Features: |
939 | 972 | |
940 | * [apispec.core]: Add ``extra_fields`` parameter to `APISpec.definition` (:issue:`110`). Thanks :user:`lafrech` for the PR. | |
941 | * [apispec.ext.marshmallow]: Preserve the order of ``choices`` (:issue:`113`). Thanks :user:`frol` for the PR. | |
942 | ||
943 | Bug fixes: | |
944 | ||
945 | * [apispec.ext.marshmallow]: 'discriminator' is no longer valid as field metadata. It should be defined by passing ``extra_fields={'discriminator': '...'}`` to `APISpec.definition`. Thanks for reporting, :user:`lafrech`. | |
946 | * [apispec.ext.marshmallow]: Allow additional properties when translating ``Nested`` fields using ``allOf`` (:issue:`108`). Thanks :user:`lafrech` for the suggestion and the PR. | |
947 | * [apispec.ext.marshmallow]: Respect ``dump_only`` and ``load_only`` specified in ``class Meta`` (:issue:`84`). Thanks :user:`lafrech` for the fix. | |
948 | ||
949 | Other changes: | |
950 | ||
951 | * Drop support for Python 3.3. | |
973 | - [apispec.core]: Add ``extra_fields`` parameter to `APISpec.definition` (:issue:`110`). Thanks :user:`lafrech` for the PR. | |
974 | - [apispec.ext.marshmallow]: Preserve the order of ``choices`` (:issue:`113`). Thanks :user:`frol` for the PR. | |
975 | ||
976 | Bug fixes: | |
977 | ||
978 | - [apispec.ext.marshmallow]: 'discriminator' is no longer valid as field metadata. It should be defined by passing ``extra_fields={'discriminator': '...'}`` to `APISpec.definition`. Thanks for reporting, :user:`lafrech`. | |
979 | - [apispec.ext.marshmallow]: Allow additional properties when translating ``Nested`` fields using ``allOf`` (:issue:`108`). Thanks :user:`lafrech` for the suggestion and the PR. | |
980 | - [apispec.ext.marshmallow]: Respect ``dump_only`` and ``load_only`` specified in ``class Meta`` (:issue:`84`). Thanks :user:`lafrech` for the fix. | |
981 | ||
982 | Other changes: | |
983 | ||
984 | - Drop support for Python 3.3. | |
952 | 985 | |
953 | 986 | |
954 | 987 | 0.18.0 (2017-02-19) |
956 | 989 | |
957 | 990 | Features: |
958 | 991 | |
959 | * [apispec.ext.marshmallow]: Translate ``allow_none`` on ``Fields`` to ``x-nullable`` (:issue:`66`). Thanks :user:`lafrech`. | |
992 | - [apispec.ext.marshmallow]: Translate ``allow_none`` on ``Fields`` to ``x-nullable`` (:issue:`66`). Thanks :user:`lafrech`. | |
960 | 993 | |
961 | 994 | 0.17.4 (2017-02-16) |
962 | 995 | +++++++++++++++++++ |
963 | 996 | |
964 | 997 | Bug fixes: |
965 | 998 | |
966 | * [apispec.ext.marshmallow]: Fix corruption of ``Schema._declared_fields`` when serializing an APISpec (:issue:`107`). Thanks :user:`serebrov` for the catch and patch. | |
999 | - [apispec.ext.marshmallow]: Fix corruption of ``Schema._declared_fields`` when serializing an APISpec (:issue:`107`). Thanks :user:`serebrov` for the catch and patch. | |
967 | 1000 | |
968 | 1001 | 0.17.3 (2017-01-21) |
969 | 1002 | +++++++++++++++++++ |
970 | 1003 | |
971 | 1004 | Bug fixes: |
972 | 1005 | |
973 | * [apispec.ext.marshmallow]: Fix behavior when passing `Schema` instances to `APISpec.definition`. The `Schema's` class will correctly be registered as a an available `ref` (:issue:`84`). Thanks :user:`lafrech` for reporting and for the PR. | |
1006 | - [apispec.ext.marshmallow]: Fix behavior when passing `Schema` instances to `APISpec.definition`. The `Schema's` class will correctly be registered as a an available `ref` (:issue:`84`). Thanks :user:`lafrech` for reporting and for the PR. | |
974 | 1007 | |
975 | 1008 | 0.17.2 (2017-01-03) |
976 | 1009 | +++++++++++++++++++ |
977 | 1010 | |
978 | 1011 | Bug fixes: |
979 | 1012 | |
980 | * [apispec.ext.tornado]: Remove usage of ``inspect.getargspec`` for Python >= 3.3 (:issue:`102`). Thanks :user:`matijabesednik`. | |
1013 | - [apispec.ext.tornado]: Remove usage of ``inspect.getargspec`` for Python >= 3.3 (:issue:`102`). Thanks :user:`matijabesednik`. | |
981 | 1014 | |
982 | 1015 | 0.17.1 (2016-11-19) |
983 | 1016 | +++++++++++++++++++ |
984 | 1017 | |
985 | 1018 | Bug fixes: |
986 | 1019 | |
987 | * [apispec.ext.marshmallow]: Prevent unnecessary warning when generating specs for marshmallow Schema's with autogenerated fields (:issue:`95`). Thanks :user:`khorolets` reporting and for the PR. | |
988 | * [apispec.ext.marshmallow]: Correctly translate ``Length`` validator to `minItems` and `maxItems` for array-type fields (``Nested`` and ``List``) (:issue:`97`). Thanks :user:`YuriHeupa` for reporting and for the PR. | |
1020 | - [apispec.ext.marshmallow]: Prevent unnecessary warning when generating specs for marshmallow Schema's with autogenerated fields (:issue:`95`). Thanks :user:`khorolets` reporting and for the PR. | |
1021 | - [apispec.ext.marshmallow]: Correctly translate ``Length`` validator to `minItems` and `maxItems` for array-type fields (``Nested`` and ``List``) (:issue:`97`). Thanks :user:`YuriHeupa` for reporting and for the PR. | |
989 | 1022 | |
990 | 1023 | 0.17.0 (2016-10-30) |
991 | 1024 | +++++++++++++++++++ |
992 | 1025 | |
993 | 1026 | Features: |
994 | 1027 | |
995 | * [apispec.ext.marshmallow]: Add support for properties that start with `x-`. Thanks :user:`martinlatrille` for the PR. | |
1028 | - [apispec.ext.marshmallow]: Add support for properties that start with `x-`. Thanks :user:`martinlatrille` for the PR. | |
996 | 1029 | |
997 | 1030 | 0.16.0 (2016-10-12) |
998 | 1031 | +++++++++++++++++++ |
999 | 1032 | |
1000 | 1033 | Features: |
1001 | 1034 | |
1002 | * [apispec.core]: Allow ``description`` to be passed to ``APISpec.definition`` (:issue:`93`). Thanks :user:`martinlatrille`. | |
1035 | - [apispec.core]: Allow ``description`` to be passed to ``APISpec.definition`` (:issue:`93`). Thanks :user:`martinlatrille`. | |
1003 | 1036 | |
1004 | 1037 | 0.15.0 (2016-10-02) |
1005 | 1038 | +++++++++++++++++++ |
1006 | 1039 | |
1007 | 1040 | Features: |
1008 | 1041 | |
1009 | * [apispec.ext.marshmallow]: Allow ``'query'`` to be passed as a field location (:issue:`89`). Thanks :user:`lafrech`. | |
1010 | ||
1011 | Bug fixes: | |
1012 | ||
1013 | * [apispec.ext.flask]: Properly strip off ``basePath`` when ``APPLICATION_ROOT`` is set on a Flask app's config (:issue:`78`). Thanks :user:`deckar01` for reporting and :user:`asteinlein` for the PR. | |
1042 | - [apispec.ext.marshmallow]: Allow ``'query'`` to be passed as a field location (:issue:`89`). Thanks :user:`lafrech`. | |
1043 | ||
1044 | Bug fixes: | |
1045 | ||
1046 | - [apispec.ext.flask]: Properly strip off ``basePath`` when ``APPLICATION_ROOT`` is set on a Flask app's config (:issue:`78`). Thanks :user:`deckar01` for reporting and :user:`asteinlein` for the PR. | |
1014 | 1047 | |
1015 | 1048 | 0.14.0 (2016-08-14) |
1016 | 1049 | +++++++++++++++++++ |
1017 | 1050 | |
1018 | 1051 | Features: |
1019 | 1052 | |
1020 | * [apispec.core]: Maintain order in which paths are added to a spec (:issue:`87`). Thanks :user:`ranjanashish` for the PR. | |
1021 | * [apispec.ext.marshmallow]: Maintain order of fields when ``ordered=True`` on Schema. Thanks again :user:`ranjanashish`. | |
1053 | - [apispec.core]: Maintain order in which paths are added to a spec (:issue:`87`). Thanks :user:`ranjanashish` for the PR. | |
1054 | - [apispec.ext.marshmallow]: Maintain order of fields when ``ordered=True`` on Schema. Thanks again :user:`ranjanashish`. | |
1022 | 1055 | |
1023 | 1056 | 0.13.0 (2016-07-03) |
1024 | 1057 | +++++++++++++++++++ |
1025 | 1058 | |
1026 | 1059 | Features: |
1027 | 1060 | |
1028 | * [apispec.ext.marshmallow]: Add support for ``Dict`` field (:issue:`80`). Thanks :user:`ericb` for the PR. | |
1029 | * [apispec.ext.marshmallow]: ``dump_only`` fields add ``readOnly`` flag in OpenAPI spec (:issue:`79`). Thanks :user:`itajaja` for the suggestion and PR. | |
1030 | ||
1031 | Bug fixes: | |
1032 | ||
1033 | * [apispec.ext.marshmallow]: Properly exclude nested dump-only fields from parameters (:issue:`82`). Thanks :user:`incognick` for the catch and patch. | |
1061 | - [apispec.ext.marshmallow]: Add support for ``Dict`` field (:issue:`80`). Thanks :user:`ericb` for the PR. | |
1062 | - [apispec.ext.marshmallow]: ``dump_only`` fields add ``readOnly`` flag in OpenAPI spec (:issue:`79`). Thanks :user:`itajaja` for the suggestion and PR. | |
1063 | ||
1064 | Bug fixes: | |
1065 | ||
1066 | - [apispec.ext.marshmallow]: Properly exclude nested dump-only fields from parameters (:issue:`82`). Thanks :user:`incognick` for the catch and patch. | |
1034 | 1067 | |
1035 | 1068 | Support: |
1036 | 1069 | |
1037 | * Update tasks.py for compatibility with invoke>=0.13.0. | |
1070 | - Update tasks.py for compatibility with invoke>=0.13.0. | |
1038 | 1071 | |
1039 | 1072 | 0.12.0 (2016-05-22) |
1040 | 1073 | +++++++++++++++++++ |
1041 | 1074 | |
1042 | 1075 | Features: |
1043 | 1076 | |
1044 | * [apispec.ext.marshmallow]: Inspect validators to set additional attributes (:issue:`66`). Thanks :user:`deckar01` for the PR. | |
1045 | ||
1046 | Bug fixes: | |
1047 | ||
1048 | * [apispec.ext.marshmallow]: Respect ``partial`` parameters on ``Schemas`` (:issue:`74`). Thanks :user:`incognick` for reporting. | |
1077 | - [apispec.ext.marshmallow]: Inspect validators to set additional attributes (:issue:`66`). Thanks :user:`deckar01` for the PR. | |
1078 | ||
1079 | Bug fixes: | |
1080 | ||
1081 | - [apispec.ext.marshmallow]: Respect ``partial`` parameters on ``Schemas`` (:issue:`74`). Thanks :user:`incognick` for reporting. | |
1049 | 1082 | |
1050 | 1083 | 0.11.1 (2016-05-02) |
1051 | 1084 | +++++++++++++++++++ |
1052 | 1085 | |
1053 | 1086 | Bug fixes: |
1054 | 1087 | |
1055 | * [apispec.ext.flask]: Flask plugin respects ``APPLICATION_ROOT`` from app's config (:issue:`69`). Thanks :user:`deckar01` for the catch and patch. | |
1056 | * [apispec.ext.marshmallow]: Fix support for plural schema instances (:issue:`71`). Thanks again :user:`deckar01`. | |
1088 | - [apispec.ext.flask]: Flask plugin respects ``APPLICATION_ROOT`` from app's config (:issue:`69`). Thanks :user:`deckar01` for the catch and patch. | |
1089 | - [apispec.ext.marshmallow]: Fix support for plural schema instances (:issue:`71`). Thanks again :user:`deckar01`. | |
1057 | 1090 | |
1058 | 1091 | 0.11.0 (2016-04-12) |
1059 | 1092 | +++++++++++++++++++ |
1060 | 1093 | |
1061 | 1094 | Features: |
1062 | 1095 | |
1063 | * Support vendor extensions on paths (:issue:`65`). Thanks :user:`lucascosta` for the PR. | |
1064 | * *Backwards-incompatible*: Remove support for old versions (<=0.15.0) of webargs. | |
1065 | ||
1066 | Bug fixes: | |
1067 | ||
1068 | * Fix error message when plugin does not have a ``setup()`` function. | |
1069 | * [apispec.ext.marshmallow] Fix bug in introspecting self-referencing marshmallow fields, i.e. ``fields.Nested('self')`` (:issue:`55`). Thanks :user:`whoiswes` for reporting. | |
1070 | * [apispec.ext.marshmallow] ``field2property`` no longer pops off ``location`` from a field's metadata (:issue:`67`). | |
1096 | - Support vendor extensions on paths (:issue:`65`). Thanks :user:`lucascosta` for the PR. | |
1097 | - *Backwards-incompatible*: Remove support for old versions (<=0.15.0) of webargs. | |
1098 | ||
1099 | Bug fixes: | |
1100 | ||
1101 | - Fix error message when plugin does not have a ``setup()`` function. | |
1102 | - [apispec.ext.marshmallow] Fix bug in introspecting self-referencing marshmallow fields, i.e. ``fields.Nested('self')`` (:issue:`55`). Thanks :user:`whoiswes` for reporting. | |
1103 | - [apispec.ext.marshmallow] ``field2property`` no longer pops off ``location`` from a field's metadata (:issue:`67`). | |
1071 | 1104 | |
1072 | 1105 | Support: |
1073 | 1106 | |
1074 | * Lots of new docs, including a User Guide and improved extension docs. | |
1107 | - Lots of new docs, including a User Guide and improved extension docs. | |
1075 | 1108 | |
1076 | 1109 | 0.10.1 (2016-04-09) |
1077 | 1110 | +++++++++++++++++++ |
1080 | 1113 | |
1081 | 1114 | Features: |
1082 | 1115 | |
1083 | * Add Tornado extension (:issue:`62`). | |
1084 | ||
1085 | Bug fixes: | |
1086 | ||
1087 | * Compatibility fix with marshmallow>=2.7.0 (:issue:`64`). | |
1088 | * Fix bug that raised error for Swagger parameters that didn't include the ``in`` key (:issue:`63`). | |
1116 | - Add Tornado extension (:issue:`62`). | |
1117 | ||
1118 | Bug fixes: | |
1119 | ||
1120 | - Compatibility fix with marshmallow>=2.7.0 (:issue:`64`). | |
1121 | - Fix bug that raised error for Swagger parameters that didn't include the ``in`` key (:issue:`63`). | |
1089 | 1122 | |
1090 | 1123 | Big thanks :user:`lucascosta` for all these changes. |
1091 | 1124 | |
1094 | 1127 | |
1095 | 1128 | Bug fixes: |
1096 | 1129 | |
1097 | * Fix generation of metadata for ``Nested`` fields (:issue:`61`). Thanks :user:`martinlatrille`. | |
1130 | - Fix generation of metadata for ``Nested`` fields (:issue:`61`). Thanks :user:`martinlatrille`. | |
1098 | 1131 | |
1099 | 1132 | 0.9.0 (2016-03-13) |
1100 | 1133 | ++++++++++++++++++ |
1101 | 1134 | |
1102 | 1135 | Features: |
1103 | 1136 | |
1104 | * Add ``APISpec.add_tags`` method for adding Swagger tags. Thanks :user:`martinlatrille`. | |
1105 | ||
1106 | Bug fixes: | |
1107 | ||
1108 | * Fix bug in marshmallow extension where metadata was being lost when converting marshmallow ``Schemas`` when ``many=False``. Thanks again :user:`martinlatrille`. | |
1109 | ||
1110 | Other changes: | |
1111 | ||
1112 | * Remove duplicate ``SWAGGER_VERSION`` from ``api.ext.marshmallow.swagger``. | |
1137 | - Add ``APISpec.add_tags`` method for adding Swagger tags. Thanks :user:`martinlatrille`. | |
1138 | ||
1139 | Bug fixes: | |
1140 | ||
1141 | - Fix bug in marshmallow extension where metadata was being lost when converting marshmallow ``Schemas`` when ``many=False``. Thanks again :user:`martinlatrille`. | |
1142 | ||
1143 | Other changes: | |
1144 | ||
1145 | - Remove duplicate ``SWAGGER_VERSION`` from ``api.ext.marshmallow.swagger``. | |
1113 | 1146 | |
1114 | 1147 | Support: |
1115 | 1148 | |
1116 | * Update docs to reflect rename of Swagger to OpenAPI. | |
1149 | - Update docs to reflect rename of Swagger to OpenAPI. | |
1117 | 1150 | |
1118 | 1151 | |
1119 | 1152 | 0.8.0 (2016-03-06) |
1121 | 1154 | |
1122 | 1155 | Features: |
1123 | 1156 | |
1124 | * ``apispec.ext.marshmallow.swagger.schema2jsonschema`` properly introspects ``Schema`` instances when ``many=True`` (:issue:`53`). Thanks :user:`frol` for the PR. | |
1125 | ||
1126 | Bug fixes: | |
1127 | ||
1128 | * Fix error reporting when an invalid object is passed to ``schema2jsonschema`` or ``schema2parameters`` (:issue:`52`). Thanks again :user:`frol`. | |
1157 | - ``apispec.ext.marshmallow.swagger.schema2jsonschema`` properly introspects ``Schema`` instances when ``many=True`` (:issue:`53`). Thanks :user:`frol` for the PR. | |
1158 | ||
1159 | Bug fixes: | |
1160 | ||
1161 | - Fix error reporting when an invalid object is passed to ``schema2jsonschema`` or ``schema2parameters`` (:issue:`52`). Thanks again :user:`frol`. | |
1129 | 1162 | |
1130 | 1163 | 0.7.0 (2016-02-11) |
1131 | 1164 | ++++++++++++++++++ |
1132 | 1165 | |
1133 | 1166 | Features: |
1134 | 1167 | |
1135 | * ``APISpec.add_path`` accepts ``Path`` objects (:issue:`49`). Thanks :user:`Trii` for the suggestion and the implementation. | |
1136 | ||
1137 | Bug fixes: | |
1138 | ||
1139 | * Use correct field name in "required" array when ``load_from`` and ``dump_to`` are used (:issue:`48`). Thanks :user:`benbeadle` for the catch and patch. | |
1168 | - ``APISpec.add_path`` accepts ``Path`` objects (:issue:`49`). Thanks :user:`Trii` for the suggestion and the implementation. | |
1169 | ||
1170 | Bug fixes: | |
1171 | ||
1172 | - Use correct field name in "required" array when ``load_from`` and ``dump_to`` are used (:issue:`48`). Thanks :user:`benbeadle` for the catch and patch. | |
1140 | 1173 | |
1141 | 1174 | 0.6.0 (2016-01-04) |
1142 | 1175 | ++++++++++++++++++ |
1143 | 1176 | |
1144 | 1177 | Features: |
1145 | 1178 | |
1146 | * Add ``APISpec#add_parameter`` for adding common Swagger parameter objects. Thanks :user:`jta`. | |
1147 | * The field name in a spec will be adjusted if a ``Field's`` ``load_from`` and ``dump_to`` attributes are the same. :issue:`43`. Thanks again :user:`jta`. | |
1148 | ||
1149 | Bug fixes: | |
1150 | ||
1151 | * Fix bug that caused a stack overflow when adding nested Schemas to an ``APISpec`` (:issue:`31`, :issue:`41`). Thanks :user:`alapshin` and :user:`itajaja` for reporting. Thanks :user:`itajaja` for the patch. | |
1179 | - Add ``APISpec#add_parameter`` for adding common Swagger parameter objects. Thanks :user:`jta`. | |
1180 | - The field name in a spec will be adjusted if a ``Field's`` ``load_from`` and ``dump_to`` attributes are the same. :issue:`43`. Thanks again :user:`jta`. | |
1181 | ||
1182 | Bug fixes: | |
1183 | ||
1184 | - Fix bug that caused a stack overflow when adding nested Schemas to an ``APISpec`` (:issue:`31`, :issue:`41`). Thanks :user:`alapshin` and :user:`itajaja` for reporting. Thanks :user:`itajaja` for the patch. | |
1152 | 1185 | |
1153 | 1186 | 0.5.0 (2015-12-13) |
1154 | 1187 | ++++++++++++++++++ |
1155 | 1188 | |
1156 | * ``schema2jsonschema`` and ``schema2parameters`` can introspect a marshmallow ``Schema`` instance as well as a ``Schema`` class (:issue:`37`). Thanks :user:`frol`. | |
1157 | * *Backwards-incompatible*: The first argument to ``schema2jsonschema`` and ``schema2parameters`` was changed from ``schema_cls`` to ``schema``. | |
1158 | ||
1159 | Bug fixes: | |
1160 | ||
1161 | * Handle conflicting signatures for plugin helpers. Thanks :user:`AndrewPashkin` for the catch and patch. | |
1189 | - ``schema2jsonschema`` and ``schema2parameters`` can introspect a marshmallow ``Schema`` instance as well as a ``Schema`` class (:issue:`37`). Thanks :user:`frol`. | |
1190 | - *Backwards-incompatible*: The first argument to ``schema2jsonschema`` and ``schema2parameters`` was changed from ``schema_cls`` to ``schema``. | |
1191 | ||
1192 | Bug fixes: | |
1193 | ||
1194 | - Handle conflicting signatures for plugin helpers. Thanks :user:`AndrewPashkin` for the catch and patch. | |
1162 | 1195 | |
1163 | 1196 | 0.4.2 (2015-11-23) |
1164 | 1197 | ++++++++++++++++++ |
1165 | 1198 | |
1166 | * Skip dump-only fields when ``dump=False`` is passed to ``schema2parameters`` and ``fields2parameters``. Thanks :user:`frol`. | |
1167 | ||
1168 | Bug fixes: | |
1169 | ||
1170 | * Raise ``SwaggerError`` when ``validate_swagger`` fails. Thanks :user:`frol`. | |
1199 | - Skip dump-only fields when ``dump=False`` is passed to ``schema2parameters`` and ``fields2parameters``. Thanks :user:`frol`. | |
1200 | ||
1201 | Bug fixes: | |
1202 | ||
1203 | - Raise ``SwaggerError`` when ``validate_swagger`` fails. Thanks :user:`frol`. | |
1171 | 1204 | |
1172 | 1205 | 0.4.1 (2015-10-19) |
1173 | 1206 | ++++++++++++++++++ |
1174 | 1207 | |
1175 | * Correctly pass ``dump`` parameter to ``field2parameters``. | |
1208 | - Correctly pass ``dump`` parameter to ``field2parameters``. | |
1176 | 1209 | |
1177 | 1210 | 0.4.0 (2015-10-18) |
1178 | 1211 | ++++++++++++++++++ |
1179 | 1212 | |
1180 | * Add ``dump`` parameter to ``field2property`` (:issue:`32`). | |
1213 | - Add ``dump`` parameter to ``field2property`` (:issue:`32`). | |
1181 | 1214 | |
1182 | 1215 | 0.3.0 (2015-10-02) |
1183 | 1216 | ++++++++++++++++++ |
1184 | 1217 | |
1185 | * Rename and repackage as "apispec". | |
1186 | * Support ``enum`` field of JSON Schema based on ``OneOf`` and ``ContainsOnly`` validators. | |
1218 | - Rename and repackage as "apispec". | |
1219 | - Support ``enum`` field of JSON Schema based on ``OneOf`` and ``ContainsOnly`` validators. | |
1187 | 1220 | |
1188 | 1221 | 0.2.0 (2015-09-27) |
1189 | 1222 | ++++++++++++++++++ |
1190 | 1223 | |
1191 | * Add ``schema2parameters``, ``fields2parameters``, and ``field2parameters``. | |
1192 | * Removed ``Fixed`` from ``swagger.FIELD_MAPPING`` for compatibility with marshmallow>=2.0.0. | |
1224 | - Add ``schema2parameters``, ``fields2parameters``, and ``field2parameters``. | |
1225 | - Removed ``Fixed`` from ``swagger.FIELD_MAPPING`` for compatibility with marshmallow>=2.0.0. | |
1193 | 1226 | |
1194 | 1227 | 0.1.0 (2015-09-13) |
1195 | 1228 | ++++++++++++++++++ |
1196 | 1229 | |
1197 | * First release. | |
1230 | - First release. |
0 | 0 | include LICENSE |
1 | 1 | include *.rst |
2 | include src/apispec/py.typed | |
2 | 3 | recursive-include tests * |
3 | 4 | recursive-include docs * |
4 | 5 | recursive-exclude docs *.pyc |
42 | 42 | |
43 | 43 | $ pip install -U apispec |
44 | 44 | |
45 | When using marshmallow pluging, ensure a compatible marshmallow version is used: :: | |
45 | When using the marshmallow plugin, ensure a compatible marshmallow version is used: :: | |
46 | 46 | |
47 | 47 | $ pip install -U apispec[marshmallow] |
48 | 48 |
0 | trigger: | |
1 | branches: | |
2 | include: [dev, test-me-*] | |
3 | tags: | |
4 | include: ['*'] | |
5 | ||
6 | # Run builds nightly to catch incompatibilities with new marshmallow releases | |
7 | schedules: | |
8 | - cron: "0 0 * * *" | |
9 | displayName: Daily midnight build | |
10 | branches: | |
11 | include: | |
12 | - dev | |
13 | always: "true" | |
14 | ||
15 | resources: | |
16 | repositories: | |
17 | - repository: sloria | |
18 | type: github | |
19 | endpoint: github | |
20 | name: sloria/azure-pipeline-templates | |
21 | ref: refs/heads/sloria | |
22 | ||
23 | jobs: | |
24 | - template: job--python-tox.yml@sloria | |
25 | parameters: | |
26 | toxenvs: | |
27 | - lint | |
28 | ||
29 | - py36-marshmallow3 | |
30 | - py37-marshmallow3 | |
31 | - py38-marshmallow3 | |
32 | - py39-marshmallow3 | |
33 | ||
34 | - py39-marshmallowdev | |
35 | ||
36 | - docs | |
37 | os: linux | |
38 | - template: job--pypi-release.yml@sloria | |
39 | parameters: | |
40 | python: "3.9" | |
41 | distributions: "sdist bdist_wheel" | |
42 | dependsOn: | |
43 | - tox_linux |
71 | 71 | # 'title': 'Gisty', |
72 | 72 | # 'version': '1.0.0'}, |
73 | 73 | # 'openapi': '3.0.2', |
74 | # 'paths': OrderedDict([('/gist/{gist_id}', | |
75 | # {'get': {'responses': {'200': {'content': {'application/json': {'schema': {'$ref': '#/definitions/Gist'}}}}}}})]), | |
74 | # 'paths': {'/gist/{gist_id}': | |
75 | # {'get': {'responses': {'200': {'content': {'application/json': {'schema': {'$ref': '#/definitions/Gist'}}}}}}}}, | |
76 | 76 | # 'tags': []} |
77 | 77 | |
78 | 78 | Use `to_yaml <apispec.APISpec.to_yaml>` to export your spec to YAML. |
1 | 1 | =========================== |
2 | 2 | |
3 | 3 | This section documents migration paths to new releases. |
4 | ||
5 | Upgrading to 5.0.0 | |
6 | ------------------ | |
7 | ||
8 | Upgrading to 4.0.0 | |
9 | ------------------ | |
10 | ||
11 | location is ignored in field metadata | |
12 | ************************************* | |
13 | ||
14 | ``location`` parameter is ignored in ``Field`` metadata. A ``Schema`` can only | |
15 | have a single location. | |
16 | ||
17 | A ``Schema`` with fields from different locations must be split into multiple | |
18 | ``Schema``s. | |
19 | ||
20 | Upgrading to 3.0.0 | |
21 | ------------------ | |
4 | 22 | |
5 | 23 | Upgrading to 2.0.0 |
6 | 24 | ------------------ |
23 | 41 | """Path helper that parses docstrings for operations. Adds a |
24 | 42 | ``func`` parameter to `apispec.APISpec.path`. |
25 | 43 | """ |
26 | operations = load_operations_from_docstring(func.__doc__) | |
27 | return Path(path=path, operations=operations) | |
44 | operations.update(load_operations_from_docstring(func.__doc__)) | |
28 | 45 | |
29 | 46 | Components must be referenced by ID, not full path |
30 | 47 | ************************************************** |
99 | 99 | # 'title': 'Gisty', |
100 | 100 | # 'version': '1.0.0'}, |
101 | 101 | # 'openapi': '3.0.2', |
102 | # 'paths': OrderedDict(), | |
102 | # 'paths': {}, | |
103 | 103 | # 'tags': []} |
104 | 104 | |
105 | 105 | Our application will have a Flask route for the gist detail endpoint. |
155 | 155 | # 'title': 'Gisty', |
156 | 156 | # 'version': '1.0.0'}, |
157 | 157 | # 'openapi': '3.0.2', |
158 | # 'paths': OrderedDict([('/gists/{gist_id}', | |
159 | # OrderedDict([('get', | |
160 | # {'parameters': [{'in': 'path', | |
158 | # 'paths': {'/gists/{gist_id}': {'get': {'parameters': [{'in': 'path', | |
161 | 159 | # 'name': 'gist_id', |
162 | 160 | # 'required': True, |
163 | 161 | # 'schema': {'format': 'int32', |
164 | 162 | # 'type': 'integer'}}], |
165 | # 'responses': {200: {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Gist'}}}}}})]))]), | |
163 | # 'responses': {200: {'content': {'application/json': {'schema': {'$ref': '#/components/schemas/Gist'}}}}}}}}, | |
166 | 164 | # 'tags': []} |
167 | 165 | |
168 | 166 | If your API uses `method-based dispatching <http://flask.pocoo.org/docs/0.12/views/#method-based-dispatching>`_, the process is similar. Note that the method no longer needs to be included in the docstring. |
23 | 23 | |
24 | 24 | .. code-block:: python |
25 | 25 | |
26 | from apispec import Path, BasePlugin | |
26 | from apispec import BasePlugin | |
27 | 27 | from apispec.yaml_utils import load_operations_from_docstring |
28 | 28 | |
29 | 29 | |
146 | 146 | |
147 | 147 | spec.path(path="/gists/{gist_id}", func=gist_detail) |
148 | 148 | print(dict(spec.to_dict()["paths"])) |
149 | # {'/gists/{gist_id}': OrderedDict([('get', {'responses': {200: {'content': {'application/json': {'schema': '#/definitions/Gist'}}}}})])} | |
149 | # {'/gists/{gist_id}': {'get': {'responses': {200: {'content': {'application/json': {'schema': '#/definitions/Gist'}}}}}}} | |
150 | 150 | |
151 | 151 | |
152 | 152 | Next Steps |
5 | 5 | max-line-length = 110 |
6 | 6 | max-complexity = 18 |
7 | 7 | select = B,C,E,F,W,T4,B9 |
8 | ||
9 | [mypy] | |
10 | ignore_missing_imports = true | |
11 | warn_unreachable = true | |
12 | warn_unused_ignores = true | |
13 | warn_redundant_casts = true | |
14 | no_implicit_optional = true |
4 | 4 | "marshmallow": ["marshmallow>=3.13.0"], |
5 | 5 | "yaml": ["PyYAML>=3.10"], |
6 | 6 | "validation": ["prance[osv]>=0.11"], |
7 | "lint": ["flake8==3.9.2", "flake8-bugbear==21.9.1", "pre-commit~=2.4"], | |
7 | "lint": [ | |
8 | "flake8==4.0.1", | |
9 | "flake8-bugbear==22.4.25", | |
10 | "pre-commit~=2.4", | |
11 | "mypy==0.950", | |
12 | "types-PyYAML", | |
13 | ], | |
8 | 14 | "docs": [ |
9 | 15 | "marshmallow>=3.13.0", |
10 | "pyyaml==5.4.1", | |
11 | "sphinx==4.2.0", | |
12 | "sphinx-issues==1.2.0", | |
16 | "pyyaml==6.0", | |
17 | "sphinx==4.5.0", | |
18 | "sphinx-issues==3.0.1", | |
13 | 19 | "sphinx-rtd-theme==1.0.0", |
14 | 20 | ], |
15 | 21 | } |
60 | 66 | license="MIT", |
61 | 67 | zip_safe=False, |
62 | 68 | keywords="apispec swagger openapi specification oas documentation spec rest api", |
63 | python_requires=">=3.6", | |
69 | python_requires=">=3.7", | |
64 | 70 | classifiers=[ |
65 | 71 | "License :: OSI Approved :: MIT License", |
66 | 72 | "Programming Language :: Python :: 3", |
67 | "Programming Language :: Python :: 3.6", | |
68 | 73 | "Programming Language :: Python :: 3.7", |
69 | 74 | "Programming Language :: Python :: 3.8", |
70 | 75 | "Programming Language :: Python :: 3.9", |
76 | "Programming Language :: Python :: 3.10", | |
71 | 77 | "Programming Language :: Python :: 3 :: Only", |
72 | 78 | ], |
73 | 79 | test_suite="tests", |
2 | 2 | from .core import APISpec |
3 | 3 | from .plugin import BasePlugin |
4 | 4 | |
5 | __version__ = "5.1.1" | |
5 | __version__ = "5.2.2" | |
6 | 6 | __all__ = ["APISpec", "BasePlugin"] |
0 | 0 | """Core apispec classes and functions.""" |
1 | from collections import OrderedDict | |
1 | ||
2 | from __future__ import annotations | |
3 | from collections.abc import Sequence | |
4 | ||
5 | ||
6 | import typing | |
2 | 7 | from copy import deepcopy |
3 | 8 | import warnings |
4 | 9 | |
11 | 16 | ) |
12 | 17 | from .utils import OpenAPIVersion, deepupdate, COMPONENT_SUBSECTIONS, build_reference |
13 | 18 | |
19 | if typing.TYPE_CHECKING: | |
20 | from .plugin import BasePlugin | |
21 | ||
22 | ||
14 | 23 | VALID_METHODS_OPENAPI_V2 = ["get", "post", "put", "patch", "delete", "head", "options"] |
15 | 24 | |
16 | 25 | VALID_METHODS_OPENAPI_V3 = VALID_METHODS_OPENAPI_V2 + ["trace"] |
25 | 34 | They became sub-fields of "components" top-level field in OAS v3. |
26 | 35 | """ |
27 | 36 | |
28 | def __init__(self, plugins, openapi_version): | |
37 | def __init__( | |
38 | self, | |
39 | plugins: Sequence[BasePlugin], | |
40 | openapi_version: OpenAPIVersion, | |
41 | ) -> None: | |
29 | 42 | self._plugins = plugins |
30 | 43 | self.openapi_version = openapi_version |
31 | self.schemas = {} | |
32 | self.responses = {} | |
33 | self.parameters = {} | |
34 | self.headers = {} | |
35 | self.examples = {} | |
36 | self.security_schemes = {} | |
37 | self.schemas_lazy = {} | |
38 | self.responses_lazy = {} | |
39 | self.parameters_lazy = {} | |
40 | self.headers_lazy = {} | |
41 | self.examples_lazy = {} | |
44 | self.schemas: dict[str, dict] = {} | |
45 | self.responses: dict[str, dict] = {} | |
46 | self.parameters: dict[str, dict] = {} | |
47 | self.headers: dict[str, dict] = {} | |
48 | self.examples: dict[str, dict] = {} | |
49 | self.security_schemes: dict[str, dict] = {} | |
50 | self.schemas_lazy: dict[str, dict] = {} | |
51 | self.responses_lazy: dict[str, dict] = {} | |
52 | self.parameters_lazy: dict[str, dict] = {} | |
53 | self.headers_lazy: dict[str, dict] = {} | |
54 | self.examples_lazy: dict[str, dict] = {} | |
42 | 55 | |
43 | 56 | self._subsections = { |
44 | 57 | "schema": self.schemas, |
56 | 69 | "example": self.examples_lazy, |
57 | 70 | } |
58 | 71 | |
59 | def to_dict(self): | |
72 | def to_dict(self) -> dict[str, dict]: | |
60 | 73 | return { |
61 | 74 | COMPONENT_SUBSECTIONS[self.openapi_version.major][k]: v |
62 | 75 | for k, v in self._subsections.items() |
63 | 76 | if v != {} |
64 | 77 | } |
65 | 78 | |
66 | def _register_component(self, obj_type, component_id, component, *, lazy=False): | |
79 | def _register_component( | |
80 | self, | |
81 | obj_type: str, | |
82 | component_id: str, | |
83 | component: dict, | |
84 | *, | |
85 | lazy: bool = False, | |
86 | ) -> None: | |
67 | 87 | subsection = (self._subsections if lazy is False else self._subsections_lazy)[ |
68 | 88 | obj_type |
69 | 89 | ] |
70 | 90 | subsection[component_id] = component |
71 | 91 | |
72 | def _do_register_lazy_component(self, obj_type, component_id): | |
92 | def _do_register_lazy_component( | |
93 | self, | |
94 | obj_type: str, | |
95 | component_id: str, | |
96 | ) -> None: | |
73 | 97 | component_buffer = self._subsections_lazy[obj_type] |
74 | 98 | # If component was lazy registered, register it for real |
75 | 99 | if component_id in component_buffer: |
77 | 101 | component_id |
78 | 102 | ) |
79 | 103 | |
80 | def get_ref(self, obj_type, obj_or_component_id): | |
104 | def get_ref( | |
105 | self, | |
106 | obj_type: str, | |
107 | obj_or_component_id: dict | str, | |
108 | ) -> dict: | |
81 | 109 | """Return object or reference |
82 | 110 | |
83 | 111 | If obj is a dict, it is assumed to be a complete description and it is returned as is. |
95 | 123 | obj_type, self.openapi_version.major, obj_or_component_id |
96 | 124 | ) |
97 | 125 | |
98 | def schema(self, component_id, component=None, *, lazy=False, **kwargs): | |
126 | def schema( | |
127 | self, | |
128 | component_id: str, | |
129 | component: dict | None = None, | |
130 | *, | |
131 | lazy: bool = False, | |
132 | **kwargs: typing.Any, | |
133 | ) -> Components: | |
99 | 134 | """Add a new schema to the spec. |
100 | 135 | |
101 | 136 | :param str component_id: identifier by which schema may be referenced |
135 | 170 | self._register_component("schema", component_id, ret, lazy=lazy) |
136 | 171 | return self |
137 | 172 | |
138 | def response(self, component_id, component=None, *, lazy=False, **kwargs): | |
173 | def response( | |
174 | self, | |
175 | component_id: str, | |
176 | component: dict | None = None, | |
177 | *, | |
178 | lazy: bool = False, | |
179 | **kwargs: typing.Any, | |
180 | ) -> Components: | |
139 | 181 | """Add a response which can be referenced. |
140 | 182 | |
141 | 183 | :param str component_id: ref_id to use as reference |
159 | 201 | return self |
160 | 202 | |
161 | 203 | def parameter( |
162 | self, component_id, location, component=None, *, lazy=False, **kwargs | |
163 | ): | |
204 | self, | |
205 | component_id: str, | |
206 | location: str, | |
207 | component: dict | None = None, | |
208 | *, | |
209 | lazy: bool = False, | |
210 | **kwargs: typing.Any, | |
211 | ) -> Components: | |
164 | 212 | """Add a parameter which can be referenced. |
165 | 213 | |
166 | 214 | :param str component_id: identifier by which parameter may be referenced |
191 | 239 | self._register_component("parameter", component_id, ret, lazy=lazy) |
192 | 240 | return self |
193 | 241 | |
194 | def header(self, component_id, component, *, lazy=False, **kwargs): | |
242 | def header( | |
243 | self, | |
244 | component_id: str, | |
245 | component: dict, | |
246 | *, | |
247 | lazy: bool = False, | |
248 | **kwargs: typing.Any, | |
249 | ) -> Components: | |
195 | 250 | """Add a header which can be referenced. |
196 | 251 | |
197 | 252 | :param str component_id: identifier by which header may be referenced |
216 | 271 | self._register_component("header", component_id, ret, lazy=lazy) |
217 | 272 | return self |
218 | 273 | |
219 | def example(self, component_id, component, *, lazy=False): | |
274 | def example( | |
275 | self, component_id: str, component: dict, *, lazy: bool = False | |
276 | ) -> Components: | |
220 | 277 | """Add an example which can be referenced |
221 | 278 | |
222 | 279 | :param str component_id: identifier by which example may be referenced |
232 | 289 | self._register_component("example", component_id, component, lazy=lazy) |
233 | 290 | return self |
234 | 291 | |
235 | def security_scheme(self, component_id, component): | |
292 | def security_scheme(self, component_id: str, component: dict) -> Components: | |
236 | 293 | """Add a security scheme which can be referenced. |
237 | 294 | |
238 | 295 | :param str component_id: component_id to use as reference |
245 | 302 | self._register_component("security_scheme", component_id, component) |
246 | 303 | return self |
247 | 304 | |
248 | def _resolve_schema(self, obj): | |
305 | def _resolve_schema(self, obj) -> None: | |
249 | 306 | """Replace schema reference as string with a $ref if needed |
250 | 307 | |
251 | 308 | Also resolve references in the schema |
254 | 311 | obj["schema"] = self.get_ref("schema", obj["schema"]) |
255 | 312 | self._resolve_refs_in_schema(obj["schema"]) |
256 | 313 | |
257 | def _resolve_examples(self, obj): | |
314 | def _resolve_examples(self, obj) -> None: | |
258 | 315 | """Replace example reference as string with a $ref""" |
259 | 316 | for name, example in obj.get("examples", {}).items(): |
260 | 317 | obj["examples"][name] = self.get_ref("example", example) |
261 | 318 | |
262 | def _resolve_refs_in_schema(self, schema): | |
319 | def _resolve_refs_in_schema(self, schema: dict) -> None: | |
263 | 320 | if "properties" in schema: |
264 | 321 | for key in schema["properties"]: |
265 | 322 | schema["properties"][key] = self.get_ref( |
278 | 335 | schema["not"] = self.get_ref("schema", schema["not"]) |
279 | 336 | self._resolve_refs_in_schema(schema["not"]) |
280 | 337 | |
281 | def _resolve_refs_in_parameter_or_header(self, parameter_or_header): | |
338 | def _resolve_refs_in_parameter_or_header(self, parameter_or_header) -> None: | |
282 | 339 | self._resolve_schema(parameter_or_header) |
283 | 340 | self._resolve_examples(parameter_or_header) |
284 | 341 | |
285 | def _resolve_refs_in_request_body(self, request_body): | |
342 | def _resolve_refs_in_request_body(self, request_body) -> None: | |
286 | 343 | # requestBody is OpenAPI v3+ |
287 | 344 | for media_type in request_body["content"].values(): |
288 | 345 | self._resolve_schema(media_type) |
289 | 346 | self._resolve_examples(media_type) |
290 | 347 | |
291 | def _resolve_refs_in_response(self, response): | |
348 | def _resolve_refs_in_response(self, response) -> None: | |
292 | 349 | if self.openapi_version.major < 3: |
293 | 350 | self._resolve_schema(response) |
294 | 351 | else: |
300 | 357 | self._resolve_refs_in_parameter_or_header(response["headers"][name]) |
301 | 358 | # TODO: Resolve link refs when Components supports links |
302 | 359 | |
303 | def _resolve_refs_in_operation(self, operation): | |
360 | def _resolve_refs_in_operation(self, operation) -> None: | |
304 | 361 | if "parameters" in operation: |
305 | 362 | parameters = [] |
306 | 363 | for parameter in operation["parameters"]: |
311 | 368 | if "requestBody" in operation: |
312 | 369 | self._resolve_refs_in_request_body(operation["requestBody"]) |
313 | 370 | if "responses" in operation: |
314 | responses = OrderedDict() | |
371 | responses = {} | |
315 | 372 | for code, response in operation["responses"].items(): |
316 | 373 | response = self.get_ref("response", response) |
317 | 374 | self._resolve_refs_in_response(response) |
318 | 375 | responses[code] = response |
319 | 376 | operation["responses"] = responses |
320 | 377 | |
321 | def resolve_refs_in_path(self, path): | |
378 | def resolve_refs_in_path(self, path) -> None: | |
322 | 379 | if "parameters" in path: |
323 | 380 | parameters = [] |
324 | 381 | for parameter in path["parameters"]: |
353 | 410 | See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#openapi-object |
354 | 411 | """ |
355 | 412 | |
356 | def __init__(self, title, version, openapi_version, plugins=(), **options): | |
413 | def __init__( | |
414 | self, | |
415 | title: str, | |
416 | version: str, | |
417 | openapi_version: OpenAPIVersion | str, | |
418 | plugins: Sequence[BasePlugin] = (), | |
419 | **options: typing.Any, | |
420 | ) -> None: | |
357 | 421 | self.title = title |
358 | 422 | self.version = version |
359 | 423 | self.openapi_version = OpenAPIVersion(openapi_version) |
361 | 425 | self.plugins = plugins |
362 | 426 | |
363 | 427 | # Metadata |
364 | self._tags = [] | |
365 | self._paths = OrderedDict() | |
428 | self._tags: list[dict] = [] | |
429 | self._paths: dict = {} | |
366 | 430 | |
367 | 431 | # Components |
368 | 432 | self.components = Components(self.plugins, self.openapi_version) |
371 | 435 | for plugin in self.plugins: |
372 | 436 | plugin.init_spec(self) |
373 | 437 | |
374 | def to_dict(self): | |
375 | ret = { | |
438 | def to_dict(self) -> dict[str, typing.Any]: | |
439 | ret: dict[str, typing.Any] = { | |
376 | 440 | "paths": self._paths, |
377 | 441 | "info": {"title": self.title, "version": self.version}, |
378 | 442 | } |
389 | 453 | ret = deepupdate(ret, self.options) |
390 | 454 | return ret |
391 | 455 | |
392 | def to_yaml(self, yaml_dump_kwargs=None): | |
456 | def to_yaml(self, yaml_dump_kwargs: typing.Any | None = None) -> str: | |
393 | 457 | """Render the spec to YAML. Requires PyYAML to be installed. |
394 | 458 | |
395 | 459 | :param dict yaml_dump_kwargs: Additional keyword arguments to pass to `yaml.dump` |
398 | 462 | |
399 | 463 | return dict_to_yaml(self.to_dict(), yaml_dump_kwargs) |
400 | 464 | |
401 | def tag(self, tag): | |
465 | def tag(self, tag: dict) -> APISpec: | |
402 | 466 | """Store information about a tag. |
403 | 467 | |
404 | 468 | :param dict tag: the dictionary storing information about the tag. |
408 | 472 | |
409 | 473 | def path( |
410 | 474 | self, |
411 | path=None, | |
475 | path: str | None = None, | |
412 | 476 | *, |
413 | operations=None, | |
414 | summary=None, | |
415 | description=None, | |
416 | parameters=None, | |
417 | **kwargs, | |
418 | ): | |
477 | operations: dict[str, typing.Any] | None = None, | |
478 | summary: str | None = None, | |
479 | description: str | None = None, | |
480 | parameters: list[dict] | None = None, | |
481 | **kwargs: typing.Any, | |
482 | ) -> APISpec: | |
419 | 483 | """Add a new path object to the spec. |
420 | 484 | |
421 | 485 | https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#path-item-object |
429 | 493 | """ |
430 | 494 | # operations and parameters must be deepcopied because they are mutated |
431 | 495 | # in _clean_operations and operation helpers and path may be called twice |
432 | operations = deepcopy(operations) or OrderedDict() | |
496 | operations = deepcopy(operations) or {} | |
433 | 497 | parameters = deepcopy(parameters) or [] |
434 | 498 | |
435 | 499 | # Execute path helpers |
467 | 531 | |
468 | 532 | return self |
469 | 533 | |
470 | def _clean_parameters(self, parameters): | |
534 | def _clean_parameters( | |
535 | self, | |
536 | parameters: list[dict], | |
537 | ) -> list[dict]: | |
471 | 538 | """Ensure that all parameters with "in" equal to "path" are also required |
472 | 539 | as required by the OpenAPI specification, as well as normalizing any |
473 | 540 | references to global parameters and checking for duplicates parameters |
503 | 570 | |
504 | 571 | return parameters |
505 | 572 | |
506 | def _clean_operations(self, operations): | |
573 | def _clean_operations( | |
574 | self, | |
575 | operations: dict[str, dict], | |
576 | ) -> None: | |
507 | 577 | """Ensure that all parameters with "in" equal to "path" are also required |
508 | 578 | as required by the OpenAPI specification, as well as normalizing any |
509 | 579 | references to global parameters. Also checks for invalid HTTP methods. |
528 | 598 | operation["parameters"] |
529 | 599 | ) |
530 | 600 | if "responses" in operation: |
531 | responses = OrderedDict() | |
601 | responses = {} | |
532 | 602 | for code, response in operation["responses"].items(): |
533 | 603 | try: |
534 | 604 | code = int(code) # handles IntEnums like http.HTTPStatus |
68 | 68 | # 'type': 'object'}} |
69 | 69 | |
70 | 70 | """ |
71 | ||
72 | from __future__ import annotations | |
73 | ||
71 | 74 | import warnings |
72 | ||
73 | from apispec import BasePlugin | |
75 | import typing | |
76 | ||
77 | from marshmallow import Schema | |
78 | ||
79 | from apispec import BasePlugin, APISpec | |
80 | from apispec.utils import OpenAPIVersion | |
74 | 81 | from .common import resolve_schema_instance, make_schema_key, resolve_schema_cls |
75 | 82 | from .openapi import OpenAPIConverter |
76 | 83 | from .schema_resolver import SchemaResolver |
77 | 84 | |
78 | 85 | |
79 | def resolver(schema): | |
86 | def resolver(schema: type[Schema]) -> str: | |
80 | 87 | """Default schema name resolver function that strips 'Schema' from the end of the class name.""" |
81 | 88 | schema_cls = resolve_schema_cls(schema) |
82 | 89 | name = schema_cls.__name__ |
105 | 112 | Converter = OpenAPIConverter |
106 | 113 | Resolver = SchemaResolver |
107 | 114 | |
108 | def __init__(self, schema_name_resolver=None): | |
115 | def __init__( | |
116 | self, | |
117 | schema_name_resolver: typing.Callable[[type[Schema]], str] | None = None, | |
118 | ) -> None: | |
109 | 119 | super().__init__() |
110 | 120 | self.schema_name_resolver = schema_name_resolver or resolver |
111 | self.spec = None | |
112 | self.openapi_version = None | |
113 | self.converter = None | |
114 | self.resolver = None | |
115 | ||
116 | def init_spec(self, spec): | |
121 | self.spec: APISpec | None = None | |
122 | self.openapi_version: OpenAPIVersion | None = None | |
123 | self.converter: OpenAPIConverter | None = None | |
124 | self.resolver: SchemaResolver | None = None | |
125 | ||
126 | def init_spec(self, spec: APISpec) -> None: | |
117 | 127 | super().init_spec(spec) |
118 | 128 | self.spec = spec |
119 | 129 | self.openapi_version = spec.openapi_version |
186 | 196 | self.resolver.resolve_response(response) |
187 | 197 | return response |
188 | 198 | |
189 | def header_helper(self, header, **kwargs): | |
199 | def header_helper(self, header: dict, **kwargs: typing.Any): | |
190 | 200 | """Header component helper that allows using a marshmallow |
191 | 201 | :class:`Schema <marshmallow.Schema>` in header definition. |
192 | 202 | |
193 | 203 | :param dict header: header fields. May contain a marshmallow |
194 | 204 | Schema class or instance. |
195 | 205 | """ |
206 | assert self.resolver # needed for mypy | |
196 | 207 | self.resolver.resolve_schema(header) |
197 | 208 | return header |
198 | 209 | |
199 | def operation_helper(self, operations, **kwargs): | |
210 | def operation_helper( | |
211 | self, | |
212 | path: str | None = None, | |
213 | operations: dict | None = None, | |
214 | **kwargs: typing.Any, | |
215 | ) -> None: | |
216 | assert self.resolver # needed for mypy | |
200 | 217 | self.resolver.resolve_operations(operations) |
201 | 218 | |
202 | def warn_if_schema_already_in_spec(self, schema_key): | |
219 | def warn_if_schema_already_in_spec(self, schema_key: str) -> None: | |
203 | 220 | """Method to warn the user if the schema has already been added to the |
204 | 221 | spec. |
205 | 222 | """ |
223 | assert self.converter # needed for mypy | |
206 | 224 | if schema_key in self.converter.refs: |
207 | 225 | warnings.warn( |
208 | 226 | "{} has already been added to the spec. Adding it twice may " |
0 | 0 | """Utilities to get schema instances/classes""" |
1 | ||
2 | from __future__ import annotations | |
1 | 3 | |
2 | 4 | import copy |
3 | 5 | import warnings |
4 | from collections import namedtuple, OrderedDict | |
6 | ||
7 | from apispec.core import Components | |
5 | 8 | |
6 | 9 | import marshmallow |
7 | 10 | |
9 | 12 | MODIFIERS = ["only", "exclude", "load_only", "dump_only", "partial"] |
10 | 13 | |
11 | 14 | |
12 | def resolve_schema_instance(schema): | |
15 | def resolve_schema_instance( | |
16 | schema: type[marshmallow.Schema] | marshmallow.Schema | str, | |
17 | ) -> marshmallow.Schema: | |
13 | 18 | """Return schema instance for given schema (instance or class). |
14 | 19 | |
15 | 20 | :param type|Schema|str schema: instance, class or class name of marshmallow.Schema |
19 | 24 | return schema() |
20 | 25 | if isinstance(schema, marshmallow.Schema): |
21 | 26 | return schema |
22 | return marshmallow.class_registry.get_class(schema)() | |
27 | return marshmallow.class_registry.get_class(schema)() # type: ignore | |
23 | 28 | |
24 | 29 | |
25 | 30 | def resolve_schema_cls(schema): |
35 | 40 | return marshmallow.class_registry.get_class(schema) |
36 | 41 | |
37 | 42 | |
38 | def get_fields(schema, *, exclude_dump_only=False): | |
43 | def get_fields(schema, *, exclude_dump_only: bool = False): | |
39 | 44 | """Return fields from schema. |
40 | 45 | |
41 | 46 | :param Schema schema: A marshmallow Schema instance or a class object |
71 | 76 | ) |
72 | 77 | |
73 | 78 | |
74 | def filter_excluded_fields(fields, Meta, *, exclude_dump_only): | |
79 | def filter_excluded_fields(fields, Meta, *, exclude_dump_only: bool) -> dict: | |
75 | 80 | """Filter fields that should be ignored in the OpenAPI spec. |
76 | 81 | |
77 | 82 | :param dict fields: A dictionary of fields name field object pairs |
82 | 87 | if exclude_dump_only: |
83 | 88 | exclude.extend(getattr(Meta, "dump_only", [])) |
84 | 89 | |
85 | filtered_fields = OrderedDict( | |
86 | (key, value) | |
90 | filtered_fields = { | |
91 | key: value | |
87 | 92 | for key, value in fields.items() |
88 | 93 | if key not in exclude and not (exclude_dump_only and value.dump_only) |
89 | ) | |
94 | } | |
90 | 95 | |
91 | 96 | return filtered_fields |
92 | 97 | |
93 | 98 | |
94 | def make_schema_key(schema): | |
99 | def make_schema_key(schema: marshmallow.Schema) -> tuple: | |
95 | 100 | if not isinstance(schema, marshmallow.Schema): |
96 | 101 | raise TypeError("can only make a schema key based on a Schema instance.") |
97 | 102 | modifiers = [] |
104 | 109 | # Unhashable iterable (list, set) |
105 | 110 | attribute = frozenset(attribute) |
106 | 111 | modifiers.append(attribute) |
107 | return SchemaKey(schema.__class__, *modifiers) | |
112 | return tuple([schema.__class__, *modifiers]) | |
108 | 113 | |
109 | 114 | |
110 | SchemaKey = namedtuple("SchemaKey", ["SchemaClass"] + MODIFIERS) | |
111 | ||
112 | ||
113 | def get_unique_schema_name(components, name, counter=0): | |
115 | def get_unique_schema_name(components: Components, name: str, counter: int = 0) -> str: | |
114 | 116 | """Function to generate a unique name based on the provided name and names |
115 | 117 | already in the spec. Will append a number to the name to make it unique if |
116 | 118 | the name is already in the spec. |
8 | 8 | import re |
9 | 9 | import functools |
10 | 10 | import operator |
11 | import typing | |
11 | 12 | import warnings |
12 | 13 | |
13 | 14 | import marshmallow |
14 | 15 | from marshmallow.orderedset import OrderedSet |
16 | ||
17 | from apispec.utils import OpenAPIVersion | |
15 | 18 | |
16 | 19 | |
17 | 20 | RegexType = type(re.compile("")) |
86 | 89 | """Adds methods for converting marshmallow fields to an OpenAPI properties.""" |
87 | 90 | |
88 | 91 | field_mapping = DEFAULT_FIELD_MAPPING |
92 | openapi_version: OpenAPIVersion | |
89 | 93 | |
90 | 94 | def init_attribute_functions(self): |
91 | 95 | self.attribute_functions = [ |
153 | 157 | setattr(self, func.__name__, bound_func) |
154 | 158 | self.attribute_functions.append(bound_func) |
155 | 159 | |
156 | def field2property(self, field): | |
160 | def field2property(self, field: marshmallow.fields.Field) -> dict: | |
157 | 161 | """Return the JSON Schema property definition given a marshmallow |
158 | 162 | :class:`Field <marshmallow.fields.Field>`. |
159 | 163 | |
165 | 169 | :param Field field: A marshmallow field. |
166 | 170 | :rtype: dict, a Property Object |
167 | 171 | """ |
168 | ret = {} | |
172 | ret: dict = {} | |
169 | 173 | |
170 | 174 | for attr_func in self.attribute_functions: |
171 | 175 | ret.update(attr_func(field, ret=ret)) |
172 | 176 | |
173 | 177 | return ret |
174 | 178 | |
175 | def field2type_and_format(self, field, **kwargs): | |
179 | def field2type_and_format( | |
180 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
181 | ) -> dict: | |
176 | 182 | """Return the dictionary of OpenAPI type and format based on the field type. |
177 | 183 | |
178 | 184 | :param Field field: A marshmallow field. |
201 | 207 | |
202 | 208 | return ret |
203 | 209 | |
204 | def field2default(self, field, **kwargs): | |
210 | def field2default( | |
211 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
212 | ) -> dict: | |
205 | 213 | """Return the dictionary containing the field's default value. |
206 | 214 | |
207 | 215 | Will first look for a `default` key in the field's metadata and then |
217 | 225 | else: |
218 | 226 | default = field.load_default |
219 | 227 | if default is not marshmallow.missing and not callable(default): |
220 | default = field._serialize(default, None, None) | |
228 | default = field._serialize(default, None, None) # type:ignore | |
221 | 229 | ret["default"] = default |
222 | 230 | return ret |
223 | 231 | |
224 | def field2choices(self, field, **kwargs): | |
232 | def field2choices( | |
233 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
234 | ) -> dict: | |
225 | 235 | """Return the dictionary of OpenAPI field attributes for valid choices definition. |
226 | 236 | |
227 | 237 | :param Field field: A marshmallow field. |
230 | 240 | attributes = {} |
231 | 241 | |
232 | 242 | comparable = [ |
233 | validator.comparable | |
243 | validator.comparable # type:ignore | |
234 | 244 | for validator in field.validators |
235 | 245 | if hasattr(validator, "comparable") |
236 | 246 | ] |
238 | 248 | attributes["enum"] = comparable |
239 | 249 | else: |
240 | 250 | choices = [ |
241 | OrderedSet(validator.choices) | |
251 | OrderedSet(validator.choices) # type:ignore | |
242 | 252 | for validator in field.validators |
243 | 253 | if hasattr(validator, "choices") |
244 | 254 | ] |
247 | 257 | |
248 | 258 | return attributes |
249 | 259 | |
250 | def field2read_only(self, field, **kwargs): | |
260 | def field2read_only( | |
261 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
262 | ) -> dict: | |
251 | 263 | """Return the dictionary of OpenAPI field attributes for a dump_only field. |
252 | 264 | |
253 | 265 | :param Field field: A marshmallow field. |
258 | 270 | attributes["readOnly"] = True |
259 | 271 | return attributes |
260 | 272 | |
261 | def field2write_only(self, field, **kwargs): | |
273 | def field2write_only( | |
274 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
275 | ) -> dict: | |
262 | 276 | """Return the dictionary of OpenAPI field attributes for a load_only field. |
263 | 277 | |
264 | 278 | :param Field field: A marshmallow field. |
269 | 283 | attributes["writeOnly"] = True |
270 | 284 | return attributes |
271 | 285 | |
272 | def field2nullable(self, field, ret): | |
286 | def field2nullable(self, field: marshmallow.fields.Field, ret) -> dict: | |
273 | 287 | """Return the dictionary of OpenAPI field attributes for a nullable field. |
274 | 288 | |
275 | 289 | :param Field field: A marshmallow field. |
276 | 290 | :rtype: dict |
277 | 291 | """ |
278 | attributes = {} | |
292 | attributes: dict = {} | |
279 | 293 | if field.allow_none: |
280 | 294 | if self.openapi_version.major < 3: |
281 | 295 | attributes["x-nullable"] = True |
285 | 299 | attributes["type"] = [*make_type_list(ret.get("type")), "null"] |
286 | 300 | return attributes |
287 | 301 | |
288 | def field2range(self, field, ret): | |
302 | def field2range(self, field: marshmallow.fields.Field, ret) -> dict: | |
289 | 303 | """Return the dictionary of OpenAPI field attributes for a set of |
290 | 304 | :class:`Range <marshmallow.validators.Range>` validators. |
291 | 305 | |
309 | 323 | ) |
310 | 324 | return make_min_max_attributes(validators, min_attr, max_attr) |
311 | 325 | |
312 | def field2length(self, field, **kwargs): | |
326 | def field2length( | |
327 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
328 | ) -> dict: | |
313 | 329 | """Return the dictionary of OpenAPI field attributes for a set of |
314 | 330 | :class:`Length <marshmallow.validators.Length>` validators. |
315 | 331 | |
333 | 349 | max_attr = "maxItems" if is_array else "maxLength" |
334 | 350 | |
335 | 351 | equal_list = [ |
336 | validator.equal for validator in validators if validator.equal is not None | |
352 | validator.equal # type:ignore | |
353 | for validator in validators | |
354 | if validator.equal is not None # type:ignore | |
337 | 355 | ] |
338 | 356 | if equal_list: |
339 | 357 | return {min_attr: equal_list[0], max_attr: equal_list[0]} |
340 | 358 | |
341 | 359 | return make_min_max_attributes(validators, min_attr, max_attr) |
342 | 360 | |
343 | def field2pattern(self, field, **kwargs): | |
361 | def field2pattern( | |
362 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
363 | ) -> dict: | |
344 | 364 | """Return the dictionary of OpenAPI field attributes for a |
345 | 365 | :class:`Regexp <marshmallow.validators.Regexp>` validator. |
346 | 366 | |
356 | 376 | if isinstance(getattr(v, "regex", None), RegexType) |
357 | 377 | ) |
358 | 378 | v = next(regex_validators, None) |
359 | attributes = {} if v is None else {"pattern": v.regex.pattern} | |
379 | attributes = {} if v is None else {"pattern": v.regex.pattern} # type:ignore | |
360 | 380 | |
361 | 381 | if next(regex_validators, None) is not None: |
362 | 382 | warnings.warn( |
367 | 387 | |
368 | 388 | return attributes |
369 | 389 | |
370 | def metadata2properties(self, field, **kwargs): | |
390 | def metadata2properties( | |
391 | self, field: marshmallow.fields.Field, **kwargs: typing.Any | |
392 | ) -> dict: | |
371 | 393 | """Return a dictionary of properties extracted from field metadata. |
372 | 394 | |
373 | 395 | Will include field metadata that are valid properties of `OpenAPI schema |
399 | 421 | } |
400 | 422 | return ret |
401 | 423 | |
402 | def nested2properties(self, field, ret): | |
424 | def nested2properties(self, field: marshmallow.fields.Field, ret) -> dict: | |
403 | 425 | """Return a dictionary of properties from :class:`Nested <marshmallow.fields.Nested` fields. |
404 | 426 | |
405 | 427 | Typically provides a reference object and will add the schema to the spec |
415 | 437 | if isinstance(field, marshmallow.fields.Nested) and not isinstance( |
416 | 438 | field, marshmallow.fields.Pluck |
417 | 439 | ): |
418 | schema_dict = self.resolve_nested_schema(field.schema) | |
440 | schema_dict = self.resolve_nested_schema(field.schema) # type:ignore | |
419 | 441 | if ret and "$ref" in schema_dict: |
420 | 442 | ret.update({"allOf": [schema_dict]}) |
421 | 443 | else: |
422 | 444 | ret.update(schema_dict) |
423 | 445 | return ret |
424 | 446 | |
425 | def pluck2properties(self, field, **kwargs): | |
447 | def pluck2properties(self, field, **kwargs: typing.Any) -> dict: | |
426 | 448 | """Return a dictionary of properties from :class:`Pluck <marshmallow.fields.Pluck` fields. |
427 | 449 | |
428 | 450 | Pluck effectively trans-includes a field from another schema into this, |
437 | 459 | return {"type": "array", "items": ret} if field.many else ret |
438 | 460 | return {} |
439 | 461 | |
440 | def list2properties(self, field, **kwargs): | |
462 | def list2properties(self, field, **kwargs: typing.Any) -> dict: | |
441 | 463 | """Return a dictionary of properties from :class:`List <marshmallow.fields.List>` fields. |
442 | 464 | |
443 | 465 | Will provide an `items` property based on the field's `inner` attribute |
450 | 472 | ret["items"] = self.field2property(field.inner) |
451 | 473 | return ret |
452 | 474 | |
453 | def dict2properties(self, field, **kwargs): | |
475 | def dict2properties(self, field, **kwargs: typing.Any) -> dict: | |
454 | 476 | """Return a dictionary of properties from :class:`Dict <marshmallow.fields.Dict>` fields. |
455 | 477 | |
456 | 478 | Only applicable for Marshmallow versions greater than 3. Will provide an |
466 | 488 | ret["additionalProperties"] = self.field2property(value_field) |
467 | 489 | return ret |
468 | 490 | |
469 | def timedelta2properties(self, field, **kwargs): | |
491 | def timedelta2properties(self, field, **kwargs: typing.Any) -> dict: | |
470 | 492 | """Return a dictionary of properties from :class:`TimeDelta <marshmallow.fields.TimeDelta>` fields. |
471 | 493 | |
472 | 494 | Adds a `x-unit` vendor property based on the field's `precision` attribute |
495 | 517 | return types |
496 | 518 | |
497 | 519 | |
498 | def make_min_max_attributes(validators, min_attr, max_attr): | |
520 | def make_min_max_attributes(validators, min_attr, max_attr) -> dict: | |
499 | 521 | """Return a dictionary of minimum and maximum attributes based on a list |
500 | 522 | of validators. If either minimum or maximum values are not present in any |
501 | 523 | of the validator objects that attribute will be omitted. |
5 | 5 | This module is treated as private API. |
6 | 6 | Users should not need to use this module directly. |
7 | 7 | """ |
8 | from collections import OrderedDict | |
8 | ||
9 | from __future__ import annotations | |
9 | 10 | |
10 | 11 | import marshmallow |
11 | 12 | from marshmallow.utils import is_collection |
12 | 13 | |
14 | from apispec import APISpec | |
13 | 15 | from apispec.utils import OpenAPIVersion |
14 | 16 | from apispec.exceptions import APISpecError |
15 | 17 | from .field_converter import FieldConverterMixin |
46 | 48 | spec |
47 | 49 | """ |
48 | 50 | |
49 | def __init__(self, openapi_version, schema_name_resolver, spec): | |
51 | def __init__( | |
52 | self, | |
53 | openapi_version: OpenAPIVersion | str, | |
54 | schema_name_resolver, | |
55 | spec: APISpec, | |
56 | ) -> None: | |
50 | 57 | self.openapi_version = OpenAPIVersion(openapi_version) |
51 | 58 | self.schema_name_resolver = schema_name_resolver |
52 | 59 | self.spec = spec |
53 | 60 | self.init_attribute_functions() |
54 | 61 | # Schema references |
55 | self.refs = {} | |
62 | self.refs: dict = {} | |
56 | 63 | |
57 | 64 | def resolve_nested_schema(self, schema): |
58 | 65 | """Return the OpenAPI representation of a marshmallow Schema. |
78 | 85 | if not name: |
79 | 86 | try: |
80 | 87 | json_schema = self.schema2jsonschema(schema_instance) |
81 | except RuntimeError: | |
88 | except RuntimeError as exc: | |
82 | 89 | raise APISpecError( |
83 | 90 | "Name resolver returned None for schema {schema} which is " |
84 | 91 | "part of a chain of circular referencing schemas. Please" |
85 | 92 | " ensure that the schema_name_resolver passed to" |
86 | 93 | " MarshmallowPlugin returns a string for all circular" |
87 | 94 | " referencing schemas.".format(schema=schema) |
88 | ) | |
95 | ) from exc | |
89 | 96 | if getattr(schema, "many", False): |
90 | 97 | return {"type": "array", "items": json_schema} |
91 | 98 | return json_schema |
94 | 101 | return self.get_ref_dict(schema_instance) |
95 | 102 | |
96 | 103 | def schema2parameters( |
97 | self, schema, *, location, name="body", required=False, description=None | |
104 | self, | |
105 | schema, | |
106 | *, | |
107 | location, | |
108 | name: str = "body", | |
109 | required: bool = False, | |
110 | description: str | None = None, | |
98 | 111 | ): |
99 | 112 | """Return an array of OpenAPI parameters given a given marshmallow |
100 | 113 | :class:`Schema <marshmallow.Schema>`. If `location` is "body", then return an array |
134 | 147 | for field_name, field_obj in fields.items() |
135 | 148 | ] |
136 | 149 | |
137 | def _field2parameter(self, field, *, name, location): | |
150 | def _field2parameter( | |
151 | self, field: marshmallow.fields.Field, *, name: str, location: str | |
152 | ): | |
138 | 153 | """Return an OpenAPI parameter as a `dict`, given a marshmallow |
139 | 154 | :class:`Field <marshmallow.Field>`. |
140 | 155 | |
141 | 156 | https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#parameterObject |
142 | 157 | """ |
143 | ret = {"in": location, "name": name} | |
158 | ret: dict = {"in": location, "name": name} | |
144 | 159 | |
145 | 160 | partial = getattr(field.parent, "partial", False) |
146 | 161 | ret["required"] = field.required and ( |
147 | not partial or (is_collection(partial) and field.name not in partial) | |
162 | not partial | |
163 | or (is_collection(partial) and field.name not in partial) # type:ignore | |
148 | 164 | ) |
149 | 165 | |
150 | 166 | prop = self.field2property(field) |
176 | 192 | fields = get_fields(schema) |
177 | 193 | Meta = getattr(schema, "Meta", None) |
178 | 194 | partial = getattr(schema, "partial", None) |
179 | ordered = getattr(Meta, "ordered", False) | |
180 | ||
181 | jsonschema = self.fields2jsonschema(fields, partial=partial, ordered=ordered) | |
195 | ||
196 | jsonschema = self.fields2jsonschema(fields, partial=partial) | |
182 | 197 | |
183 | 198 | if hasattr(Meta, "title"): |
184 | 199 | jsonschema["title"] = Meta.title |
189 | 204 | |
190 | 205 | return jsonschema |
191 | 206 | |
192 | def fields2jsonschema(self, fields, *, ordered=False, partial=None): | |
207 | def fields2jsonschema(self, fields, *, partial=None): | |
193 | 208 | """Return the JSON Schema Object given a mapping between field names and |
194 | 209 | :class:`Field <marshmallow.Field>` objects. |
195 | 210 | |
196 | 211 | :param dict fields: A dictionary of field name field object pairs |
197 | :param bool ordered: Whether to preserve the order in which fields were declared | |
198 | 212 | :param bool|tuple partial: Whether to override a field's required flag. |
199 | 213 | If `True` no fields will be set as required. If an iterable fields |
200 | 214 | in the iterable will not be marked as required. |
201 | 215 | :rtype: dict, a JSON Schema Object |
202 | 216 | """ |
203 | jsonschema = {"type": "object", "properties": OrderedDict() if ordered else {}} | |
217 | jsonschema = {"type": "object", "properties": {}} | |
204 | 218 | |
205 | 219 | for field_name, field_obj in fields.items(): |
206 | 220 | observed_field_name = field_obj.data_key or field_name |
0 | 0 | """Base class for Plugin classes.""" |
1 | 1 | |
2 | from __future__ import annotations | |
3 | ||
4 | import typing | |
2 | 5 | |
3 | 6 | from .exceptions import PluginMethodNotImplementedError |
7 | from .core import APISpec | |
4 | 8 | |
5 | 9 | |
6 | 10 | class BasePlugin: |
7 | 11 | """Base class for APISpec plugin classes.""" |
8 | 12 | |
9 | def init_spec(self, spec): | |
13 | def init_spec(self, spec: APISpec) -> None: | |
10 | 14 | """Initialize plugin with APISpec object |
11 | 15 | |
12 | 16 | :param APISpec spec: APISpec object this plugin instance is attached to |
13 | 17 | """ |
14 | 18 | |
15 | def schema_helper(self, name, definition, **kwargs): | |
19 | def schema_helper( | |
20 | self, name: str, definition: dict, **kwargs: typing.Any | |
21 | ) -> dict | None: | |
16 | 22 | """May return definition as a dict. |
17 | 23 | |
18 | 24 | :param str name: Identifier by which schema may be referenced |
21 | 27 | """ |
22 | 28 | raise PluginMethodNotImplementedError |
23 | 29 | |
24 | def response_helper(self, response, **kwargs): | |
30 | def response_helper(self, response: dict, **kwargs: typing.Any) -> dict | None: | |
25 | 31 | """May return response component description as a dict. |
26 | 32 | |
27 | 33 | :param dict response: Response fields |
29 | 35 | """ |
30 | 36 | raise PluginMethodNotImplementedError |
31 | 37 | |
32 | def parameter_helper(self, parameter, **kwargs): | |
38 | def parameter_helper(self, parameter: dict, **kwargs: typing.Any) -> dict | None: | |
33 | 39 | """May return parameter component description as a dict. |
34 | 40 | |
35 | 41 | :param dict parameter: Parameter fields |
37 | 43 | """ |
38 | 44 | raise PluginMethodNotImplementedError |
39 | 45 | |
40 | def header_helper(self, header, **kwargs): | |
46 | def header_helper(self, header: dict, **kwargs: typing.Any) -> dict | None: | |
41 | 47 | """May return header component description as a dict. |
42 | 48 | |
43 | 49 | :param dict header: Header fields |
45 | 51 | """ |
46 | 52 | raise PluginMethodNotImplementedError |
47 | 53 | |
48 | def path_helper(self, path=None, operations=None, parameters=None, **kwargs): | |
54 | def path_helper( | |
55 | self, | |
56 | path: str | None = None, | |
57 | operations: dict | None = None, | |
58 | parameters: list[dict] | None = None, | |
59 | **kwargs: typing.Any, | |
60 | ) -> str | None: | |
49 | 61 | """May return a path as string and mutate operations dict and parameters list. |
50 | 62 | |
51 | 63 | :param str path: Path to the resource |
65 | 77 | """ |
66 | 78 | raise PluginMethodNotImplementedError |
67 | 79 | |
68 | def operation_helper(self, path=None, operations=None, **kwargs): | |
80 | def operation_helper( | |
81 | self, | |
82 | path: str | None = None, | |
83 | operations: dict | None = None, | |
84 | **kwargs: typing.Any, | |
85 | ) -> None: | |
69 | 86 | """May mutate operations. |
70 | 87 | |
71 | 88 | :param str path: Path to the resource |
0 | 0 | """Various utilities for parsing OpenAPI operations from docstrings and validating against |
1 | 1 | the OpenAPI spec. |
2 | 2 | """ |
3 | ||
4 | from __future__ import annotations | |
5 | ||
3 | 6 | import re |
4 | 7 | import json |
8 | import typing | |
5 | 9 | |
6 | 10 | from distutils import version |
7 | 11 | |
8 | 12 | from apispec import exceptions |
13 | ||
14 | if typing.TYPE_CHECKING: | |
15 | from apispec.core import APISpec | |
9 | 16 | |
10 | 17 | |
11 | 18 | COMPONENT_SUBSECTIONS = { |
26 | 33 | } |
27 | 34 | |
28 | 35 | |
29 | def build_reference(component_type, openapi_major_version, component_name): | |
36 | def build_reference( | |
37 | component_type: str, openapi_major_version: int, component_name: str | |
38 | ) -> dict[str, str]: | |
30 | 39 | """Return path to reference |
31 | 40 | |
32 | 41 | :param str component_type: Component type (schema, parameter, response, security_scheme) |
42 | 51 | } |
43 | 52 | |
44 | 53 | |
45 | def validate_spec(spec): | |
54 | def validate_spec(spec: APISpec) -> bool: | |
46 | 55 | """Validate the output of an :class:`APISpec` object against the |
47 | 56 | OpenAPI specification. |
48 | 57 | |
61 | 70 | "validate_spec requires prance to be installed. " |
62 | 71 | "You can install all validation requirements using:\n" |
63 | 72 | " pip install 'apispec[validation]'" |
64 | ) | |
73 | ) from error | |
65 | 74 | parser_kwargs = {} |
66 | 75 | if spec.openapi_version.version[0] == 3: |
67 | 76 | parser_kwargs["backend"] = "openapi-spec-validator" |
68 | 77 | try: |
69 | 78 | prance.BaseParser(spec_string=json.dumps(spec.to_dict()), **parser_kwargs) |
70 | 79 | except prance.ValidationError as err: |
71 | raise exceptions.OpenAPIError(*err.args) | |
80 | raise exceptions.OpenAPIError(*err.args) from err | |
72 | 81 | else: |
73 | 82 | return True |
74 | 83 | |
94 | 103 | MIN_INCLUSIVE_VERSION = version.LooseVersion("2.0") |
95 | 104 | MAX_EXCLUSIVE_VERSION = version.LooseVersion("4.0") |
96 | 105 | |
97 | def __init__(self, openapi_version): | |
106 | def __init__(self, openapi_version: version.LooseVersion | str) -> None: | |
98 | 107 | if isinstance(openapi_version, version.LooseVersion): |
99 | 108 | openapi_version = openapi_version.vstring |
100 | 109 | if ( |
108 | 117 | super().__init__(openapi_version) |
109 | 118 | |
110 | 119 | @property |
111 | def major(self): | |
112 | return self.version[0] | |
120 | def major(self) -> int: | |
121 | return int(self.version[0]) | |
113 | 122 | |
114 | 123 | @property |
115 | def minor(self): | |
116 | return self.version[1] | |
124 | def minor(self) -> int: | |
125 | return int(self.version[1]) | |
117 | 126 | |
118 | 127 | @property |
119 | def patch(self): | |
120 | return self.version[2] | |
128 | def patch(self) -> int: | |
129 | return int(self.version[2]) | |
121 | 130 | |
122 | 131 | |
123 | 132 | # from django.contrib.admindocs.utils |
124 | def trim_docstring(docstring): | |
133 | def trim_docstring(docstring: str) -> str: | |
125 | 134 | """Uniformly trims leading/trailing whitespace from docstrings. |
126 | 135 | |
127 | 136 | Based on http://www.python.org/peps/pep-0257.html#handling-docstring-indentation |
136 | 145 | |
137 | 146 | |
138 | 147 | # from rest_framework.utils.formatting |
139 | def dedent(content): | |
148 | def dedent(content: str) -> str: | |
140 | 149 | """ |
141 | 150 | Remove leading indent from a block of text. |
142 | 151 | Used when generating descriptions from docstrings. |
159 | 168 | |
160 | 169 | |
161 | 170 | # http://stackoverflow.com/a/8310229 |
162 | def deepupdate(original, update): | |
171 | def deepupdate(original: dict, update: dict) -> dict: | |
163 | 172 | """Recursively update a dict. |
164 | 173 | |
165 | 174 | Subdict's won't be overwritten but also updated. |
0 | 0 | """YAML utilities""" |
1 | 1 | |
2 | from collections import OrderedDict | |
2 | from __future__ import annotations | |
3 | ||
3 | 4 | import yaml |
5 | import typing | |
4 | 6 | |
5 | 7 | from apispec.utils import trim_docstring, dedent |
6 | 8 | |
7 | 9 | |
8 | class YAMLDumper(yaml.Dumper): | |
9 | @staticmethod | |
10 | def _represent_dict(dumper, instance): | |
11 | return dumper.represent_mapping("tag:yaml.org,2002:map", instance.items()) | |
10 | def dict_to_yaml(dic: dict, yaml_dump_kwargs: typing.Any | None = None) -> str: | |
11 | """Serializes a dictionary to YAML.""" | |
12 | yaml_dump_kwargs = yaml_dump_kwargs or {} | |
13 | ||
14 | # By default, don't sort alphabetically to respect schema field ordering | |
15 | yaml_dump_kwargs.setdefault("sort_keys", False) | |
16 | return yaml.dump(dic, **yaml_dump_kwargs) | |
12 | 17 | |
13 | 18 | |
14 | yaml.add_representer(OrderedDict, YAMLDumper._represent_dict, Dumper=YAMLDumper) | |
15 | ||
16 | ||
17 | def dict_to_yaml(dic, yaml_dump_kwargs=None): | |
18 | if yaml_dump_kwargs is None: | |
19 | yaml_dump_kwargs = {} | |
20 | return yaml.dump(dic, Dumper=YAMLDumper, **yaml_dump_kwargs) | |
21 | ||
22 | ||
23 | def load_yaml_from_docstring(docstring): | |
19 | def load_yaml_from_docstring(docstring: str) -> dict: | |
24 | 20 | """Loads YAML from docstring.""" |
25 | 21 | split_lines = trim_docstring(docstring).split("\n") |
26 | 22 | |
41 | 37 | PATH_KEYS = {"get", "put", "post", "delete", "options", "head", "patch"} |
42 | 38 | |
43 | 39 | |
44 | def load_operations_from_docstring(docstring): | |
40 | def load_operations_from_docstring(docstring: str) -> dict: | |
45 | 41 | """Return a dictionary of OpenAPI operations parsed from a |
46 | 42 | a docstring. |
47 | 43 | """ |
42 | 42 | class SelfReferencingSchema(Schema): |
43 | 43 | id = fields.Int() |
44 | 44 | single = fields.Nested(lambda: SelfReferencingSchema()) |
45 | many = fields.Nested(lambda: SelfReferencingSchema(many=True)) | |
45 | multiple = fields.Nested(lambda: SelfReferencingSchema(many=True)) | |
46 | 46 | |
47 | 47 | |
48 | 48 | class OrderedSchema(Schema): |
0 | 0 | import copy |
1 | from collections import OrderedDict | |
2 | 1 | from http import HTTPStatus |
3 | 2 | |
4 | 3 | import pytest |
666 | 665 | def test_path_methods_maintain_order(self, spec): |
667 | 666 | methods = ["get", "post", "put", "patch", "delete", "head", "options"] |
668 | 667 | for method in methods: |
669 | spec.path(path="/path", operations=OrderedDict({method: {}})) | |
668 | spec.path(path="/path", operations={method: {}}) | |
670 | 669 | assert list(spec.to_dict()["paths"]["/path"]) == methods |
671 | 670 | |
672 | 671 | def test_path_merges_paths(self, spec): |
1236 | 1236 | def test_self_referencing_field_many(self, spec): |
1237 | 1237 | spec.components.schema("SelfReference", schema=SelfReferencingSchema) |
1238 | 1238 | definitions = get_schemas(spec) |
1239 | result = definitions["SelfReference"]["properties"]["many"] | |
1239 | result = definitions["SelfReference"]["properties"]["multiple"] | |
1240 | 1240 | assert result == { |
1241 | 1241 | "type": "array", |
1242 | 1242 | "items": build_ref(spec, "schema", "SelfReference"), |
0 | 0 | import pytest |
1 | from collections import OrderedDict | |
2 | 1 | from datetime import datetime |
3 | 2 | |
4 | 3 | from marshmallow import EXCLUDE, fields, INCLUDE, RAISE, Schema, validate |
101 | 100 | |
102 | 101 | res = openapi.schema2jsonschema(BandSchema(partial=("drummer",))) |
103 | 102 | assert res["required"] == ["bassist"] |
104 | ||
105 | @pytest.mark.parametrize("ordered_schema", (True, False)) | |
106 | def test_ordered(self, openapi, ordered_schema): | |
107 | class BandSchema(Schema): | |
108 | class Meta: | |
109 | ordered = ordered_schema | |
110 | ||
111 | drummer = fields.Str() | |
112 | bassist = fields.Str() | |
113 | ||
114 | res = openapi.schema2jsonschema(BandSchema) | |
115 | assert isinstance(res["properties"], OrderedDict) == ordered_schema | |
116 | ||
117 | res = openapi.schema2jsonschema(BandSchema()) | |
118 | assert isinstance(res["properties"], OrderedDict) == ordered_schema | |
119 | 103 | |
120 | 104 | def test_no_required_fields(self, openapi): |
121 | 105 | class BandSchema(Schema): |
30 | 30 | def test_dict_to_yaml_unicode(): |
31 | 31 | assert yaml_utils.dict_to_yaml({"가": "나"}) == '"\\uAC00": "\\uB098"\n' |
32 | 32 | assert yaml_utils.dict_to_yaml({"가": "나"}, {"allow_unicode": True}) == "가: 나\n" |
33 | ||
34 | ||
35 | def test_dict_to_yaml_keys_are_not_sorted_by_default(): | |
36 | assert yaml_utils.dict_to_yaml({"herp": 1, "derp": 2}) == "herp: 1\nderp: 2\n" | |
37 | ||
38 | ||
39 | def test_dict_to_yaml_keys_can_be_sorted_with_yaml_dump_kwargs(): | |
40 | assert ( | |
41 | yaml_utils.dict_to_yaml( | |
42 | {"herp": 1, "derp": 2}, yaml_dump_kwargs={"sort_keys": True} | |
43 | ) | |
44 | == "derp: 2\nherp: 1\n" | |
45 | ) |