Initial public upload from Projekte/PromptBoost (part 3)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/RECORD +28 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/REQUESTED +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/WHEEL +4 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/licenses/LICENSE +3 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/licenses/LICENSE.APACHE +177 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/licenses/LICENSE.BSD +23 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/INSTALLER +1 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/METADATA +152 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/RECORD +16 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/REQUESTED +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/WHEEL +5 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE +21 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/top_level.txt +1 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/__init__.py +30 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_callers.py +169 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_hooks.py +714 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_manager.py +523 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_result.py +107 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_tracing.py +72 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_version.py +21 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_warnings.py +27 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/py.typed +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/py.py +15 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/INSTALLER +1 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/METADATA +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/RECORD +113 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/REQUESTED +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/WHEEL +4 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/licenses/LICENSE +21 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/__init__.py +456 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/__init__.py +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_config.py +386 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_core_metadata.py +97 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_core_utils.py +174 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_dataclasses.py +315 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_decorators.py +873 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_decorators_v1.py +174 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_discriminated_union.py +494 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_docs_extraction.py +113 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py +729 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_forward_ref.py +23 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py +0 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_generics.py +530 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_git.py +27 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_import_utils.py +20 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_internal_dataclass.py +7 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_known_annotated_metadata.py +403 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_mock_val_ser.py +228 -0
- personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py +868 -0
.gitattributes
CHANGED
|
@@ -38,3 +38,4 @@ personalPromptBoost/.venv/bin/python3 filter=lfs diff=lfs merge=lfs -text
|
|
| 38 |
personalPromptBoost/.venv/bin/python3.13 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
personalPromptBoost/.venv/lib/python3.13/site-packages/httptools/parser/parser.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 40 |
personalPromptBoost/.venv/lib/python3.13/site-packages/httptools/parser/url_parser.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 38 |
personalPromptBoost/.venv/bin/python3.13 filter=lfs diff=lfs merge=lfs -text
|
| 39 |
personalPromptBoost/.venv/lib/python3.13/site-packages/httptools/parser/parser.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 40 |
personalPromptBoost/.venv/lib/python3.13/site-packages/httptools/parser/url_parser.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic_core/_pydantic_core.cpython-313-x86_64-linux-gnu.so filter=lfs diff=lfs merge=lfs -text
|
personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/RECORD
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
packaging-26.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
packaging-26.1.dist-info/METADATA,sha256=7l9S61gHv-txB4r0GDduSfhr5sLI0m_N6OzcnnPjp5Q,3473
|
| 3 |
+
packaging-26.1.dist-info/RECORD,,
|
| 4 |
+
packaging-26.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 5 |
+
packaging-26.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
| 6 |
+
packaging-26.1.dist-info/licenses/LICENSE,sha256=ytHvW9NA1z4HS6YU0m996spceUDD2MNIUuZcSQlobEg,197
|
| 7 |
+
packaging-26.1.dist-info/licenses/LICENSE.APACHE,sha256=DVQuDIgE45qn836wDaWnYhSdxoLXgpRRKH4RuTjpRZQ,10174
|
| 8 |
+
packaging-26.1.dist-info/licenses/LICENSE.BSD,sha256=tw5-m3QvHMb5SLNMFqo5_-zpQZY2S8iP8NIYDwAo-sU,1344
|
| 9 |
+
packaging/__init__.py,sha256=VdPTbH5gdO5lDUfejETmJNtXxCpKSrFb15J4xMyje8w,494
|
| 10 |
+
packaging/_elffile.py,sha256=-sKkptYqzYw2-x3QByJa5mB4rfPWu1pxkZHRx1WAFCY,3211
|
| 11 |
+
packaging/_manylinux.py,sha256=Hf6nB0cOrayEs96-p3oIXAgGnFquv20DO5l-o2_Xnv0,9559
|
| 12 |
+
packaging/_musllinux.py,sha256=Z6swjH3MA7XS3qXnmMN7QPhqP3fnoYI0eQ18e9-HgAE,2707
|
| 13 |
+
packaging/_parser.py,sha256=U_DajsEx2VoC_F46fSVV3hDKNCWoQYkPkasO3dld0ig,10518
|
| 14 |
+
packaging/_tokenizer.py,sha256=tFU2Wr-ZZJdAbkXLEJo7qUQDJaIkfft9DqaifiEND7A,5391
|
| 15 |
+
packaging/dependency_groups.py,sha256=XZIAVFK9uHG4RCGprmJn3VInUWMesxha_kytJuMO9eY,10218
|
| 16 |
+
packaging/direct_url.py,sha256=eKmbDiPP1sLV4Mj_kCSZqqknrIyVO9Sr7JpF8KCjp4U,10917
|
| 17 |
+
packaging/errors.py,sha256=6hfEYXAf8v_IF65-lFadJOMIieBP2xIKtyEXjG1nGIs,2680
|
| 18 |
+
packaging/licenses/__init__.py,sha256=_Jx0XRiD_58palsWnyLrLuh59ZpGCPIPXLKdZo9OJvQ,7293
|
| 19 |
+
packaging/licenses/_spdx.py,sha256=WW7DXiyg68up_YND_wpRYlr1SHhiV4FfJLQffghhMxQ,51122
|
| 20 |
+
packaging/markers.py,sha256=8D4eXn7MPfjCOHoM0RHkgWumkokORY21pXiK_XDXnL0,15258
|
| 21 |
+
packaging/metadata.py,sha256=rK19fQhrBNv3nINqurpuo-QsCZQuWnmSEaR4Hp-76Js,38691
|
| 22 |
+
packaging/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 23 |
+
packaging/pylock.py,sha256=G_1gncTmDbRLY1jo4VDI9Uw-b5IErh_Q9V_BbVJTmD8,33890
|
| 24 |
+
packaging/requirements.py,sha256=ttyeCsLD19hoHCUChqixMKkocDGqWmXxzGZWs7IhUSA,2984
|
| 25 |
+
packaging/specifiers.py,sha256=vr9t7nC5VDqapdOqbMa2tDxJcHE_elq5rBtAh3YRwKc,65328
|
| 26 |
+
packaging/tags.py,sha256=nwfsCqWxlB3hZ8kxE9xayLykDnLLbYhAsNbOCENh6GM,32421
|
| 27 |
+
packaging/utils.py,sha256=M7-JMKic2sP1YtV_8aW7eVGB-x3ADuKCiSrsVeCd2Uo,9848
|
| 28 |
+
packaging/version.py,sha256=4XfXTSkh90jQWycNJpwJT4dBs59RSVVvJPeb30s1VnU,35427
|
personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/REQUESTED
ADDED
|
File without changes
|
personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: flit 3.12.0
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This software is made available under the terms of *either* of the licenses
|
| 2 |
+
found in LICENSE.APACHE or LICENSE.BSD. Contributions to this software is made
|
| 3 |
+
under the terms of *both* these licenses.
|
personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/licenses/LICENSE.APACHE
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
personalPromptBoost/.venv/lib/python3.13/site-packages/packaging-26.1.dist-info/licenses/LICENSE.BSD
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Copyright (c) Donald Stufft and individual contributors.
|
| 2 |
+
All rights reserved.
|
| 3 |
+
|
| 4 |
+
Redistribution and use in source and binary forms, with or without
|
| 5 |
+
modification, are permitted provided that the following conditions are met:
|
| 6 |
+
|
| 7 |
+
1. Redistributions of source code must retain the above copyright notice,
|
| 8 |
+
this list of conditions and the following disclaimer.
|
| 9 |
+
|
| 10 |
+
2. Redistributions in binary form must reproduce the above copyright
|
| 11 |
+
notice, this list of conditions and the following disclaimer in the
|
| 12 |
+
documentation and/or other materials provided with the distribution.
|
| 13 |
+
|
| 14 |
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
| 15 |
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| 16 |
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| 17 |
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
| 18 |
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
| 19 |
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
| 20 |
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
| 21 |
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
| 22 |
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
| 23 |
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: pluggy
|
| 3 |
+
Version: 1.6.0
|
| 4 |
+
Summary: plugin and hook calling mechanisms for python
|
| 5 |
+
Author-email: Holger Krekel <holger@merlinux.eu>
|
| 6 |
+
License: MIT
|
| 7 |
+
Classifier: Development Status :: 6 - Mature
|
| 8 |
+
Classifier: Intended Audience :: Developers
|
| 9 |
+
Classifier: License :: OSI Approved :: MIT License
|
| 10 |
+
Classifier: Operating System :: POSIX
|
| 11 |
+
Classifier: Operating System :: Microsoft :: Windows
|
| 12 |
+
Classifier: Operating System :: MacOS :: MacOS X
|
| 13 |
+
Classifier: Topic :: Software Development :: Testing
|
| 14 |
+
Classifier: Topic :: Software Development :: Libraries
|
| 15 |
+
Classifier: Topic :: Utilities
|
| 16 |
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
| 17 |
+
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
| 18 |
+
Classifier: Programming Language :: Python :: 3
|
| 19 |
+
Classifier: Programming Language :: Python :: 3 :: Only
|
| 20 |
+
Classifier: Programming Language :: Python :: 3.9
|
| 21 |
+
Classifier: Programming Language :: Python :: 3.10
|
| 22 |
+
Classifier: Programming Language :: Python :: 3.11
|
| 23 |
+
Classifier: Programming Language :: Python :: 3.12
|
| 24 |
+
Classifier: Programming Language :: Python :: 3.13
|
| 25 |
+
Requires-Python: >=3.9
|
| 26 |
+
Description-Content-Type: text/x-rst
|
| 27 |
+
License-File: LICENSE
|
| 28 |
+
Provides-Extra: dev
|
| 29 |
+
Requires-Dist: pre-commit; extra == "dev"
|
| 30 |
+
Requires-Dist: tox; extra == "dev"
|
| 31 |
+
Provides-Extra: testing
|
| 32 |
+
Requires-Dist: pytest; extra == "testing"
|
| 33 |
+
Requires-Dist: pytest-benchmark; extra == "testing"
|
| 34 |
+
Requires-Dist: coverage; extra == "testing"
|
| 35 |
+
Dynamic: license-file
|
| 36 |
+
|
| 37 |
+
====================================================
|
| 38 |
+
pluggy - A minimalist production ready plugin system
|
| 39 |
+
====================================================
|
| 40 |
+
|
| 41 |
+
|pypi| |conda-forge| |versions| |github-actions| |gitter| |black| |codecov|
|
| 42 |
+
|
| 43 |
+
This is the core framework used by the `pytest`_, `tox`_, and `devpi`_ projects.
|
| 44 |
+
|
| 45 |
+
Please `read the docs`_ to learn more!
|
| 46 |
+
|
| 47 |
+
A definitive example
|
| 48 |
+
====================
|
| 49 |
+
.. code-block:: python
|
| 50 |
+
|
| 51 |
+
import pluggy
|
| 52 |
+
|
| 53 |
+
hookspec = pluggy.HookspecMarker("myproject")
|
| 54 |
+
hookimpl = pluggy.HookimplMarker("myproject")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class MySpec:
|
| 58 |
+
"""A hook specification namespace."""
|
| 59 |
+
|
| 60 |
+
@hookspec
|
| 61 |
+
def myhook(self, arg1, arg2):
|
| 62 |
+
"""My special little hook that you can customize."""
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
class Plugin_1:
|
| 66 |
+
"""A hook implementation namespace."""
|
| 67 |
+
|
| 68 |
+
@hookimpl
|
| 69 |
+
def myhook(self, arg1, arg2):
|
| 70 |
+
print("inside Plugin_1.myhook()")
|
| 71 |
+
return arg1 + arg2
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class Plugin_2:
|
| 75 |
+
"""A 2nd hook implementation namespace."""
|
| 76 |
+
|
| 77 |
+
@hookimpl
|
| 78 |
+
def myhook(self, arg1, arg2):
|
| 79 |
+
print("inside Plugin_2.myhook()")
|
| 80 |
+
return arg1 - arg2
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# create a manager and add the spec
|
| 84 |
+
pm = pluggy.PluginManager("myproject")
|
| 85 |
+
pm.add_hookspecs(MySpec)
|
| 86 |
+
|
| 87 |
+
# register plugins
|
| 88 |
+
pm.register(Plugin_1())
|
| 89 |
+
pm.register(Plugin_2())
|
| 90 |
+
|
| 91 |
+
# call our ``myhook`` hook
|
| 92 |
+
results = pm.hook.myhook(arg1=1, arg2=2)
|
| 93 |
+
print(results)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
Running this directly gets us::
|
| 97 |
+
|
| 98 |
+
$ python docs/examples/toy-example.py
|
| 99 |
+
inside Plugin_2.myhook()
|
| 100 |
+
inside Plugin_1.myhook()
|
| 101 |
+
[-1, 3]
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
.. badges
|
| 105 |
+
|
| 106 |
+
.. |pypi| image:: https://img.shields.io/pypi/v/pluggy.svg
|
| 107 |
+
:target: https://pypi.org/pypi/pluggy
|
| 108 |
+
|
| 109 |
+
.. |versions| image:: https://img.shields.io/pypi/pyversions/pluggy.svg
|
| 110 |
+
:target: https://pypi.org/pypi/pluggy
|
| 111 |
+
|
| 112 |
+
.. |github-actions| image:: https://github.com/pytest-dev/pluggy/workflows/main/badge.svg
|
| 113 |
+
:target: https://github.com/pytest-dev/pluggy/actions
|
| 114 |
+
|
| 115 |
+
.. |conda-forge| image:: https://img.shields.io/conda/vn/conda-forge/pluggy.svg
|
| 116 |
+
:target: https://anaconda.org/conda-forge/pytest
|
| 117 |
+
|
| 118 |
+
.. |gitter| image:: https://badges.gitter.im/pytest-dev/pluggy.svg
|
| 119 |
+
:alt: Join the chat at https://gitter.im/pytest-dev/pluggy
|
| 120 |
+
:target: https://gitter.im/pytest-dev/pluggy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
|
| 121 |
+
|
| 122 |
+
.. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
| 123 |
+
:target: https://github.com/ambv/black
|
| 124 |
+
|
| 125 |
+
.. |codecov| image:: https://codecov.io/gh/pytest-dev/pluggy/branch/master/graph/badge.svg
|
| 126 |
+
:target: https://codecov.io/gh/pytest-dev/pluggy
|
| 127 |
+
:alt: Code coverage Status
|
| 128 |
+
|
| 129 |
+
.. links
|
| 130 |
+
.. _pytest:
|
| 131 |
+
http://pytest.org
|
| 132 |
+
.. _tox:
|
| 133 |
+
https://tox.readthedocs.org
|
| 134 |
+
.. _devpi:
|
| 135 |
+
http://doc.devpi.net
|
| 136 |
+
.. _read the docs:
|
| 137 |
+
https://pluggy.readthedocs.io/en/latest/
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
Support pluggy
|
| 141 |
+
--------------
|
| 142 |
+
|
| 143 |
+
`Open Collective`_ is an online funding platform for open and transparent communities.
|
| 144 |
+
It provides tools to raise money and share your finances in full transparency.
|
| 145 |
+
|
| 146 |
+
It is the platform of choice for individuals and companies that want to make one-time or
|
| 147 |
+
monthly donations directly to the project.
|
| 148 |
+
|
| 149 |
+
``pluggy`` is part of the ``pytest-dev`` project, see more details in the `pytest collective`_.
|
| 150 |
+
|
| 151 |
+
.. _Open Collective: https://opencollective.com
|
| 152 |
+
.. _pytest collective: https://opencollective.com/pytest
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pluggy-1.6.0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
pluggy-1.6.0.dist-info/METADATA,sha256=dDjDXuJaCV63QW-EtGHC10Qlxec0rVTDkSRTxlJE4Bw,4811
|
| 3 |
+
pluggy-1.6.0.dist-info/RECORD,,
|
| 4 |
+
pluggy-1.6.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 5 |
+
pluggy-1.6.0.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
| 6 |
+
pluggy-1.6.0.dist-info/licenses/LICENSE,sha256=1rZebCE6XQtXeRHTTW5ZSbn1nXbCOMUHGi8_wWz7JgY,1110
|
| 7 |
+
pluggy-1.6.0.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
|
| 8 |
+
pluggy/__init__.py,sha256=D6dp1gmEDjtDp8hAwQc-qrgaulnL4iltrqkLDd-g9tg,811
|
| 9 |
+
pluggy/_callers.py,sha256=gEZllGaSYVssZ2UmpNfmYC0bdVgh2jYbAFeYKvuRMjY,5991
|
| 10 |
+
pluggy/_hooks.py,sha256=E6f3nYcI6dbEuO0Gmy61ozgGU_59_e69kC08a06EBuo,25218
|
| 11 |
+
pluggy/_manager.py,sha256=K4Ip_pkEjvT2oOIfQPp8CwAWoXVnENgQRcy9tlGii0o,20219
|
| 12 |
+
pluggy/_result.py,sha256=3Xfy7DrjXbYb7puRquyY2VbidIWNq6Pp7QnuElMdj8Q,3098
|
| 13 |
+
pluggy/_tracing.py,sha256=nXd2BCmDgf8jJxV-HO3PqxR-WV53eWnF8B4AF1nJGgo,2073
|
| 14 |
+
pluggy/_version.py,sha256=5FGJNp9Lkk9uOxeCjXpoCGBF79Ar6LGPOR7-atBqb_4,511
|
| 15 |
+
pluggy/_warnings.py,sha256=td0AvZBpfamriCC3OqsLwxMh-SzAMjfjmc58T5vP3lw,828
|
| 16 |
+
pluggy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/REQUESTED
ADDED
|
File without changes
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (80.7.1)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42)
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy-1.6.0.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pluggy
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__all__ = [
|
| 2 |
+
"__version__",
|
| 3 |
+
"PluginManager",
|
| 4 |
+
"PluginValidationError",
|
| 5 |
+
"HookCaller",
|
| 6 |
+
"HookCallError",
|
| 7 |
+
"HookspecOpts",
|
| 8 |
+
"HookimplOpts",
|
| 9 |
+
"HookImpl",
|
| 10 |
+
"HookRelay",
|
| 11 |
+
"HookspecMarker",
|
| 12 |
+
"HookimplMarker",
|
| 13 |
+
"Result",
|
| 14 |
+
"PluggyWarning",
|
| 15 |
+
"PluggyTeardownRaisedWarning",
|
| 16 |
+
]
|
| 17 |
+
from ._hooks import HookCaller
|
| 18 |
+
from ._hooks import HookImpl
|
| 19 |
+
from ._hooks import HookimplMarker
|
| 20 |
+
from ._hooks import HookimplOpts
|
| 21 |
+
from ._hooks import HookRelay
|
| 22 |
+
from ._hooks import HookspecMarker
|
| 23 |
+
from ._hooks import HookspecOpts
|
| 24 |
+
from ._manager import PluginManager
|
| 25 |
+
from ._manager import PluginValidationError
|
| 26 |
+
from ._result import HookCallError
|
| 27 |
+
from ._result import Result
|
| 28 |
+
from ._version import version as __version__
|
| 29 |
+
from ._warnings import PluggyTeardownRaisedWarning
|
| 30 |
+
from ._warnings import PluggyWarning
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_callers.py
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Call loop machinery
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
from collections.abc import Generator
|
| 8 |
+
from collections.abc import Mapping
|
| 9 |
+
from collections.abc import Sequence
|
| 10 |
+
from typing import cast
|
| 11 |
+
from typing import NoReturn
|
| 12 |
+
import warnings
|
| 13 |
+
|
| 14 |
+
from ._hooks import HookImpl
|
| 15 |
+
from ._result import HookCallError
|
| 16 |
+
from ._result import Result
|
| 17 |
+
from ._warnings import PluggyTeardownRaisedWarning
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# Need to distinguish between old- and new-style hook wrappers.
|
| 21 |
+
# Wrapping with a tuple is the fastest type-safe way I found to do it.
|
| 22 |
+
Teardown = Generator[None, object, object]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def run_old_style_hookwrapper(
|
| 26 |
+
hook_impl: HookImpl, hook_name: str, args: Sequence[object]
|
| 27 |
+
) -> Teardown:
|
| 28 |
+
"""
|
| 29 |
+
backward compatibility wrapper to run a old style hookwrapper as a wrapper
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
teardown: Teardown = cast(Teardown, hook_impl.function(*args))
|
| 33 |
+
try:
|
| 34 |
+
next(teardown)
|
| 35 |
+
except StopIteration:
|
| 36 |
+
_raise_wrapfail(teardown, "did not yield")
|
| 37 |
+
try:
|
| 38 |
+
res = yield
|
| 39 |
+
result = Result(res, None)
|
| 40 |
+
except BaseException as exc:
|
| 41 |
+
result = Result(None, exc)
|
| 42 |
+
try:
|
| 43 |
+
teardown.send(result)
|
| 44 |
+
except StopIteration:
|
| 45 |
+
pass
|
| 46 |
+
except BaseException as e:
|
| 47 |
+
_warn_teardown_exception(hook_name, hook_impl, e)
|
| 48 |
+
raise
|
| 49 |
+
else:
|
| 50 |
+
_raise_wrapfail(teardown, "has second yield")
|
| 51 |
+
finally:
|
| 52 |
+
teardown.close()
|
| 53 |
+
return result.get_result()
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def _raise_wrapfail(
|
| 57 |
+
wrap_controller: Generator[None, object, object],
|
| 58 |
+
msg: str,
|
| 59 |
+
) -> NoReturn:
|
| 60 |
+
co = wrap_controller.gi_code # type: ignore[attr-defined]
|
| 61 |
+
raise RuntimeError(
|
| 62 |
+
f"wrap_controller at {co.co_name!r} {co.co_filename}:{co.co_firstlineno} {msg}"
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def _warn_teardown_exception(
|
| 67 |
+
hook_name: str, hook_impl: HookImpl, e: BaseException
|
| 68 |
+
) -> None:
|
| 69 |
+
msg = "A plugin raised an exception during an old-style hookwrapper teardown.\n"
|
| 70 |
+
msg += f"Plugin: {hook_impl.plugin_name}, Hook: {hook_name}\n"
|
| 71 |
+
msg += f"{type(e).__name__}: {e}\n"
|
| 72 |
+
msg += "For more information see https://pluggy.readthedocs.io/en/stable/api_reference.html#pluggy.PluggyTeardownRaisedWarning" # noqa: E501
|
| 73 |
+
warnings.warn(PluggyTeardownRaisedWarning(msg), stacklevel=6)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _multicall(
|
| 77 |
+
hook_name: str,
|
| 78 |
+
hook_impls: Sequence[HookImpl],
|
| 79 |
+
caller_kwargs: Mapping[str, object],
|
| 80 |
+
firstresult: bool,
|
| 81 |
+
) -> object | list[object]:
|
| 82 |
+
"""Execute a call into multiple python functions/methods and return the
|
| 83 |
+
result(s).
|
| 84 |
+
|
| 85 |
+
``caller_kwargs`` comes from HookCaller.__call__().
|
| 86 |
+
"""
|
| 87 |
+
__tracebackhide__ = True
|
| 88 |
+
results: list[object] = []
|
| 89 |
+
exception = None
|
| 90 |
+
try: # run impl and wrapper setup functions in a loop
|
| 91 |
+
teardowns: list[Teardown] = []
|
| 92 |
+
try:
|
| 93 |
+
for hook_impl in reversed(hook_impls):
|
| 94 |
+
try:
|
| 95 |
+
args = [caller_kwargs[argname] for argname in hook_impl.argnames]
|
| 96 |
+
except KeyError as e:
|
| 97 |
+
# coverage bug - this is tested
|
| 98 |
+
for argname in hook_impl.argnames: # pragma: no cover
|
| 99 |
+
if argname not in caller_kwargs:
|
| 100 |
+
raise HookCallError(
|
| 101 |
+
f"hook call must provide argument {argname!r}"
|
| 102 |
+
) from e
|
| 103 |
+
|
| 104 |
+
if hook_impl.hookwrapper:
|
| 105 |
+
function_gen = run_old_style_hookwrapper(hook_impl, hook_name, args)
|
| 106 |
+
|
| 107 |
+
next(function_gen) # first yield
|
| 108 |
+
teardowns.append(function_gen)
|
| 109 |
+
|
| 110 |
+
elif hook_impl.wrapper:
|
| 111 |
+
try:
|
| 112 |
+
# If this cast is not valid, a type error is raised below,
|
| 113 |
+
# which is the desired response.
|
| 114 |
+
res = hook_impl.function(*args)
|
| 115 |
+
function_gen = cast(Generator[None, object, object], res)
|
| 116 |
+
next(function_gen) # first yield
|
| 117 |
+
teardowns.append(function_gen)
|
| 118 |
+
except StopIteration:
|
| 119 |
+
_raise_wrapfail(function_gen, "did not yield")
|
| 120 |
+
else:
|
| 121 |
+
res = hook_impl.function(*args)
|
| 122 |
+
if res is not None:
|
| 123 |
+
results.append(res)
|
| 124 |
+
if firstresult: # halt further impl calls
|
| 125 |
+
break
|
| 126 |
+
except BaseException as exc:
|
| 127 |
+
exception = exc
|
| 128 |
+
finally:
|
| 129 |
+
if firstresult: # first result hooks return a single value
|
| 130 |
+
result = results[0] if results else None
|
| 131 |
+
else:
|
| 132 |
+
result = results
|
| 133 |
+
|
| 134 |
+
# run all wrapper post-yield blocks
|
| 135 |
+
for teardown in reversed(teardowns):
|
| 136 |
+
try:
|
| 137 |
+
if exception is not None:
|
| 138 |
+
try:
|
| 139 |
+
teardown.throw(exception)
|
| 140 |
+
except RuntimeError as re:
|
| 141 |
+
# StopIteration from generator causes RuntimeError
|
| 142 |
+
# even for coroutine usage - see #544
|
| 143 |
+
if (
|
| 144 |
+
isinstance(exception, StopIteration)
|
| 145 |
+
and re.__cause__ is exception
|
| 146 |
+
):
|
| 147 |
+
teardown.close()
|
| 148 |
+
continue
|
| 149 |
+
else:
|
| 150 |
+
raise
|
| 151 |
+
else:
|
| 152 |
+
teardown.send(result)
|
| 153 |
+
# Following is unreachable for a well behaved hook wrapper.
|
| 154 |
+
# Try to force finalizers otherwise postponed till GC action.
|
| 155 |
+
# Note: close() may raise if generator handles GeneratorExit.
|
| 156 |
+
teardown.close()
|
| 157 |
+
except StopIteration as si:
|
| 158 |
+
result = si.value
|
| 159 |
+
exception = None
|
| 160 |
+
continue
|
| 161 |
+
except BaseException as e:
|
| 162 |
+
exception = e
|
| 163 |
+
continue
|
| 164 |
+
_raise_wrapfail(teardown, "has second yield")
|
| 165 |
+
|
| 166 |
+
if exception is not None:
|
| 167 |
+
raise exception
|
| 168 |
+
else:
|
| 169 |
+
return result
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_hooks.py
ADDED
|
@@ -0,0 +1,714 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Internal hook annotation, representation and calling machinery.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
from collections.abc import Generator
|
| 8 |
+
from collections.abc import Mapping
|
| 9 |
+
from collections.abc import Sequence
|
| 10 |
+
from collections.abc import Set
|
| 11 |
+
import inspect
|
| 12 |
+
import sys
|
| 13 |
+
from types import ModuleType
|
| 14 |
+
from typing import Any
|
| 15 |
+
from typing import Callable
|
| 16 |
+
from typing import Final
|
| 17 |
+
from typing import final
|
| 18 |
+
from typing import Optional
|
| 19 |
+
from typing import overload
|
| 20 |
+
from typing import TYPE_CHECKING
|
| 21 |
+
from typing import TypedDict
|
| 22 |
+
from typing import TypeVar
|
| 23 |
+
from typing import Union
|
| 24 |
+
import warnings
|
| 25 |
+
|
| 26 |
+
from ._result import Result
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
_T = TypeVar("_T")
|
| 30 |
+
_F = TypeVar("_F", bound=Callable[..., object])
|
| 31 |
+
_Namespace = Union[ModuleType, type]
|
| 32 |
+
_Plugin = object
|
| 33 |
+
_HookExec = Callable[
|
| 34 |
+
[str, Sequence["HookImpl"], Mapping[str, object], bool],
|
| 35 |
+
Union[object, list[object]],
|
| 36 |
+
]
|
| 37 |
+
_HookImplFunction = Callable[..., Union[_T, Generator[None, Result[_T], None]]]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class HookspecOpts(TypedDict):
|
| 41 |
+
"""Options for a hook specification."""
|
| 42 |
+
|
| 43 |
+
#: Whether the hook is :ref:`first result only <firstresult>`.
|
| 44 |
+
firstresult: bool
|
| 45 |
+
#: Whether the hook is :ref:`historic <historic>`.
|
| 46 |
+
historic: bool
|
| 47 |
+
#: Whether the hook :ref:`warns when implemented <warn_on_impl>`.
|
| 48 |
+
warn_on_impl: Warning | None
|
| 49 |
+
#: Whether the hook warns when :ref:`certain arguments are requested
|
| 50 |
+
#: <warn_on_impl>`.
|
| 51 |
+
#:
|
| 52 |
+
#: .. versionadded:: 1.5
|
| 53 |
+
warn_on_impl_args: Mapping[str, Warning] | None
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class HookimplOpts(TypedDict):
|
| 57 |
+
"""Options for a hook implementation."""
|
| 58 |
+
|
| 59 |
+
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
|
| 60 |
+
wrapper: bool
|
| 61 |
+
#: Whether the hook implementation is an :ref:`old-style wrapper
|
| 62 |
+
#: <old_style_hookwrappers>`.
|
| 63 |
+
hookwrapper: bool
|
| 64 |
+
#: Whether validation against a hook specification is :ref:`optional
|
| 65 |
+
#: <optionalhook>`.
|
| 66 |
+
optionalhook: bool
|
| 67 |
+
#: Whether to try to order this hook implementation :ref:`first
|
| 68 |
+
#: <callorder>`.
|
| 69 |
+
tryfirst: bool
|
| 70 |
+
#: Whether to try to order this hook implementation :ref:`last
|
| 71 |
+
#: <callorder>`.
|
| 72 |
+
trylast: bool
|
| 73 |
+
#: The name of the hook specification to match, see :ref:`specname`.
|
| 74 |
+
specname: str | None
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@final
|
| 78 |
+
class HookspecMarker:
|
| 79 |
+
"""Decorator for marking functions as hook specifications.
|
| 80 |
+
|
| 81 |
+
Instantiate it with a project_name to get a decorator.
|
| 82 |
+
Calling :meth:`PluginManager.add_hookspecs` later will discover all marked
|
| 83 |
+
functions if the :class:`PluginManager` uses the same project name.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
__slots__ = ("project_name",)
|
| 87 |
+
|
| 88 |
+
def __init__(self, project_name: str) -> None:
|
| 89 |
+
self.project_name: Final = project_name
|
| 90 |
+
|
| 91 |
+
@overload
|
| 92 |
+
def __call__(
|
| 93 |
+
self,
|
| 94 |
+
function: _F,
|
| 95 |
+
firstresult: bool = False,
|
| 96 |
+
historic: bool = False,
|
| 97 |
+
warn_on_impl: Warning | None = None,
|
| 98 |
+
warn_on_impl_args: Mapping[str, Warning] | None = None,
|
| 99 |
+
) -> _F: ...
|
| 100 |
+
|
| 101 |
+
@overload # noqa: F811
|
| 102 |
+
def __call__( # noqa: F811
|
| 103 |
+
self,
|
| 104 |
+
function: None = ...,
|
| 105 |
+
firstresult: bool = ...,
|
| 106 |
+
historic: bool = ...,
|
| 107 |
+
warn_on_impl: Warning | None = ...,
|
| 108 |
+
warn_on_impl_args: Mapping[str, Warning] | None = ...,
|
| 109 |
+
) -> Callable[[_F], _F]: ...
|
| 110 |
+
|
| 111 |
+
def __call__( # noqa: F811
|
| 112 |
+
self,
|
| 113 |
+
function: _F | None = None,
|
| 114 |
+
firstresult: bool = False,
|
| 115 |
+
historic: bool = False,
|
| 116 |
+
warn_on_impl: Warning | None = None,
|
| 117 |
+
warn_on_impl_args: Mapping[str, Warning] | None = None,
|
| 118 |
+
) -> _F | Callable[[_F], _F]:
|
| 119 |
+
"""If passed a function, directly sets attributes on the function
|
| 120 |
+
which will make it discoverable to :meth:`PluginManager.add_hookspecs`.
|
| 121 |
+
|
| 122 |
+
If passed no function, returns a decorator which can be applied to a
|
| 123 |
+
function later using the attributes supplied.
|
| 124 |
+
|
| 125 |
+
:param firstresult:
|
| 126 |
+
If ``True``, the 1:N hook call (N being the number of registered
|
| 127 |
+
hook implementation functions) will stop at I<=N when the I'th
|
| 128 |
+
function returns a non-``None`` result. See :ref:`firstresult`.
|
| 129 |
+
|
| 130 |
+
:param historic:
|
| 131 |
+
If ``True``, every call to the hook will be memorized and replayed
|
| 132 |
+
on plugins registered after the call was made. See :ref:`historic`.
|
| 133 |
+
|
| 134 |
+
:param warn_on_impl:
|
| 135 |
+
If given, every implementation of this hook will trigger the given
|
| 136 |
+
warning. See :ref:`warn_on_impl`.
|
| 137 |
+
|
| 138 |
+
:param warn_on_impl_args:
|
| 139 |
+
If given, every implementation of this hook which requests one of
|
| 140 |
+
the arguments in the dict will trigger the corresponding warning.
|
| 141 |
+
See :ref:`warn_on_impl`.
|
| 142 |
+
|
| 143 |
+
.. versionadded:: 1.5
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
def setattr_hookspec_opts(func: _F) -> _F:
|
| 147 |
+
if historic and firstresult:
|
| 148 |
+
raise ValueError("cannot have a historic firstresult hook")
|
| 149 |
+
opts: HookspecOpts = {
|
| 150 |
+
"firstresult": firstresult,
|
| 151 |
+
"historic": historic,
|
| 152 |
+
"warn_on_impl": warn_on_impl,
|
| 153 |
+
"warn_on_impl_args": warn_on_impl_args,
|
| 154 |
+
}
|
| 155 |
+
setattr(func, self.project_name + "_spec", opts)
|
| 156 |
+
return func
|
| 157 |
+
|
| 158 |
+
if function is not None:
|
| 159 |
+
return setattr_hookspec_opts(function)
|
| 160 |
+
else:
|
| 161 |
+
return setattr_hookspec_opts
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@final
|
| 165 |
+
class HookimplMarker:
|
| 166 |
+
"""Decorator for marking functions as hook implementations.
|
| 167 |
+
|
| 168 |
+
Instantiate it with a ``project_name`` to get a decorator.
|
| 169 |
+
Calling :meth:`PluginManager.register` later will discover all marked
|
| 170 |
+
functions if the :class:`PluginManager` uses the same project name.
|
| 171 |
+
"""
|
| 172 |
+
|
| 173 |
+
__slots__ = ("project_name",)
|
| 174 |
+
|
| 175 |
+
def __init__(self, project_name: str) -> None:
|
| 176 |
+
self.project_name: Final = project_name
|
| 177 |
+
|
| 178 |
+
@overload
|
| 179 |
+
def __call__(
|
| 180 |
+
self,
|
| 181 |
+
function: _F,
|
| 182 |
+
hookwrapper: bool = ...,
|
| 183 |
+
optionalhook: bool = ...,
|
| 184 |
+
tryfirst: bool = ...,
|
| 185 |
+
trylast: bool = ...,
|
| 186 |
+
specname: str | None = ...,
|
| 187 |
+
wrapper: bool = ...,
|
| 188 |
+
) -> _F: ...
|
| 189 |
+
|
| 190 |
+
@overload # noqa: F811
|
| 191 |
+
def __call__( # noqa: F811
|
| 192 |
+
self,
|
| 193 |
+
function: None = ...,
|
| 194 |
+
hookwrapper: bool = ...,
|
| 195 |
+
optionalhook: bool = ...,
|
| 196 |
+
tryfirst: bool = ...,
|
| 197 |
+
trylast: bool = ...,
|
| 198 |
+
specname: str | None = ...,
|
| 199 |
+
wrapper: bool = ...,
|
| 200 |
+
) -> Callable[[_F], _F]: ...
|
| 201 |
+
|
| 202 |
+
def __call__( # noqa: F811
|
| 203 |
+
self,
|
| 204 |
+
function: _F | None = None,
|
| 205 |
+
hookwrapper: bool = False,
|
| 206 |
+
optionalhook: bool = False,
|
| 207 |
+
tryfirst: bool = False,
|
| 208 |
+
trylast: bool = False,
|
| 209 |
+
specname: str | None = None,
|
| 210 |
+
wrapper: bool = False,
|
| 211 |
+
) -> _F | Callable[[_F], _F]:
|
| 212 |
+
"""If passed a function, directly sets attributes on the function
|
| 213 |
+
which will make it discoverable to :meth:`PluginManager.register`.
|
| 214 |
+
|
| 215 |
+
If passed no function, returns a decorator which can be applied to a
|
| 216 |
+
function later using the attributes supplied.
|
| 217 |
+
|
| 218 |
+
:param optionalhook:
|
| 219 |
+
If ``True``, a missing matching hook specification will not result
|
| 220 |
+
in an error (by default it is an error if no matching spec is
|
| 221 |
+
found). See :ref:`optionalhook`.
|
| 222 |
+
|
| 223 |
+
:param tryfirst:
|
| 224 |
+
If ``True``, this hook implementation will run as early as possible
|
| 225 |
+
in the chain of N hook implementations for a specification. See
|
| 226 |
+
:ref:`callorder`.
|
| 227 |
+
|
| 228 |
+
:param trylast:
|
| 229 |
+
If ``True``, this hook implementation will run as late as possible
|
| 230 |
+
in the chain of N hook implementations for a specification. See
|
| 231 |
+
:ref:`callorder`.
|
| 232 |
+
|
| 233 |
+
:param wrapper:
|
| 234 |
+
If ``True`` ("new-style hook wrapper"), the hook implementation
|
| 235 |
+
needs to execute exactly one ``yield``. The code before the
|
| 236 |
+
``yield`` is run early before any non-hook-wrapper function is run.
|
| 237 |
+
The code after the ``yield`` is run after all non-hook-wrapper
|
| 238 |
+
functions have run. The ``yield`` receives the result value of the
|
| 239 |
+
inner calls, or raises the exception of inner calls (including
|
| 240 |
+
earlier hook wrapper calls). The return value of the function
|
| 241 |
+
becomes the return value of the hook, and a raised exception becomes
|
| 242 |
+
the exception of the hook. See :ref:`hookwrapper`.
|
| 243 |
+
|
| 244 |
+
:param hookwrapper:
|
| 245 |
+
If ``True`` ("old-style hook wrapper"), the hook implementation
|
| 246 |
+
needs to execute exactly one ``yield``. The code before the
|
| 247 |
+
``yield`` is run early before any non-hook-wrapper function is run.
|
| 248 |
+
The code after the ``yield`` is run after all non-hook-wrapper
|
| 249 |
+
function have run The ``yield`` receives a :class:`Result` object
|
| 250 |
+
representing the exception or result outcome of the inner calls
|
| 251 |
+
(including earlier hook wrapper calls). This option is mutually
|
| 252 |
+
exclusive with ``wrapper``. See :ref:`old_style_hookwrapper`.
|
| 253 |
+
|
| 254 |
+
:param specname:
|
| 255 |
+
If provided, the given name will be used instead of the function
|
| 256 |
+
name when matching this hook implementation to a hook specification
|
| 257 |
+
during registration. See :ref:`specname`.
|
| 258 |
+
|
| 259 |
+
.. versionadded:: 1.2.0
|
| 260 |
+
The ``wrapper`` parameter.
|
| 261 |
+
"""
|
| 262 |
+
|
| 263 |
+
def setattr_hookimpl_opts(func: _F) -> _F:
|
| 264 |
+
opts: HookimplOpts = {
|
| 265 |
+
"wrapper": wrapper,
|
| 266 |
+
"hookwrapper": hookwrapper,
|
| 267 |
+
"optionalhook": optionalhook,
|
| 268 |
+
"tryfirst": tryfirst,
|
| 269 |
+
"trylast": trylast,
|
| 270 |
+
"specname": specname,
|
| 271 |
+
}
|
| 272 |
+
setattr(func, self.project_name + "_impl", opts)
|
| 273 |
+
return func
|
| 274 |
+
|
| 275 |
+
if function is None:
|
| 276 |
+
return setattr_hookimpl_opts
|
| 277 |
+
else:
|
| 278 |
+
return setattr_hookimpl_opts(function)
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def normalize_hookimpl_opts(opts: HookimplOpts) -> None:
|
| 282 |
+
opts.setdefault("tryfirst", False)
|
| 283 |
+
opts.setdefault("trylast", False)
|
| 284 |
+
opts.setdefault("wrapper", False)
|
| 285 |
+
opts.setdefault("hookwrapper", False)
|
| 286 |
+
opts.setdefault("optionalhook", False)
|
| 287 |
+
opts.setdefault("specname", None)
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
_PYPY = hasattr(sys, "pypy_version_info")
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def varnames(func: object) -> tuple[tuple[str, ...], tuple[str, ...]]:
|
| 294 |
+
"""Return tuple of positional and keywrord argument names for a function,
|
| 295 |
+
method, class or callable.
|
| 296 |
+
|
| 297 |
+
In case of a class, its ``__init__`` method is considered.
|
| 298 |
+
For methods the ``self`` parameter is not included.
|
| 299 |
+
"""
|
| 300 |
+
if inspect.isclass(func):
|
| 301 |
+
try:
|
| 302 |
+
func = func.__init__
|
| 303 |
+
except AttributeError: # pragma: no cover - pypy special case
|
| 304 |
+
return (), ()
|
| 305 |
+
elif not inspect.isroutine(func): # callable object?
|
| 306 |
+
try:
|
| 307 |
+
func = getattr(func, "__call__", func)
|
| 308 |
+
except Exception: # pragma: no cover - pypy special case
|
| 309 |
+
return (), ()
|
| 310 |
+
|
| 311 |
+
try:
|
| 312 |
+
# func MUST be a function or method here or we won't parse any args.
|
| 313 |
+
sig = inspect.signature(
|
| 314 |
+
func.__func__ if inspect.ismethod(func) else func # type:ignore[arg-type]
|
| 315 |
+
)
|
| 316 |
+
except TypeError: # pragma: no cover
|
| 317 |
+
return (), ()
|
| 318 |
+
|
| 319 |
+
_valid_param_kinds = (
|
| 320 |
+
inspect.Parameter.POSITIONAL_ONLY,
|
| 321 |
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
| 322 |
+
)
|
| 323 |
+
_valid_params = {
|
| 324 |
+
name: param
|
| 325 |
+
for name, param in sig.parameters.items()
|
| 326 |
+
if param.kind in _valid_param_kinds
|
| 327 |
+
}
|
| 328 |
+
args = tuple(_valid_params)
|
| 329 |
+
defaults = (
|
| 330 |
+
tuple(
|
| 331 |
+
param.default
|
| 332 |
+
for param in _valid_params.values()
|
| 333 |
+
if param.default is not param.empty
|
| 334 |
+
)
|
| 335 |
+
or None
|
| 336 |
+
)
|
| 337 |
+
|
| 338 |
+
if defaults:
|
| 339 |
+
index = -len(defaults)
|
| 340 |
+
args, kwargs = args[:index], tuple(args[index:])
|
| 341 |
+
else:
|
| 342 |
+
kwargs = ()
|
| 343 |
+
|
| 344 |
+
# strip any implicit instance arg
|
| 345 |
+
# pypy3 uses "obj" instead of "self" for default dunder methods
|
| 346 |
+
if not _PYPY:
|
| 347 |
+
implicit_names: tuple[str, ...] = ("self",)
|
| 348 |
+
else: # pragma: no cover
|
| 349 |
+
implicit_names = ("self", "obj")
|
| 350 |
+
if args:
|
| 351 |
+
qualname: str = getattr(func, "__qualname__", "")
|
| 352 |
+
if inspect.ismethod(func) or ("." in qualname and args[0] in implicit_names):
|
| 353 |
+
args = args[1:]
|
| 354 |
+
|
| 355 |
+
return args, kwargs
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
@final
|
| 359 |
+
class HookRelay:
|
| 360 |
+
"""Hook holder object for performing 1:N hook calls where N is the number
|
| 361 |
+
of registered plugins."""
|
| 362 |
+
|
| 363 |
+
__slots__ = ("__dict__",)
|
| 364 |
+
|
| 365 |
+
def __init__(self) -> None:
|
| 366 |
+
""":meta private:"""
|
| 367 |
+
|
| 368 |
+
if TYPE_CHECKING:
|
| 369 |
+
|
| 370 |
+
def __getattr__(self, name: str) -> HookCaller: ...
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
# Historical name (pluggy<=1.2), kept for backward compatibility.
|
| 374 |
+
_HookRelay = HookRelay
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
_CallHistory = list[tuple[Mapping[str, object], Optional[Callable[[Any], None]]]]
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
class HookCaller:
|
| 381 |
+
"""A caller of all registered implementations of a hook specification."""
|
| 382 |
+
|
| 383 |
+
__slots__ = (
|
| 384 |
+
"name",
|
| 385 |
+
"spec",
|
| 386 |
+
"_hookexec",
|
| 387 |
+
"_hookimpls",
|
| 388 |
+
"_call_history",
|
| 389 |
+
)
|
| 390 |
+
|
| 391 |
+
def __init__(
|
| 392 |
+
self,
|
| 393 |
+
name: str,
|
| 394 |
+
hook_execute: _HookExec,
|
| 395 |
+
specmodule_or_class: _Namespace | None = None,
|
| 396 |
+
spec_opts: HookspecOpts | None = None,
|
| 397 |
+
) -> None:
|
| 398 |
+
""":meta private:"""
|
| 399 |
+
#: Name of the hook getting called.
|
| 400 |
+
self.name: Final = name
|
| 401 |
+
self._hookexec: Final = hook_execute
|
| 402 |
+
# The hookimpls list. The caller iterates it *in reverse*. Format:
|
| 403 |
+
# 1. trylast nonwrappers
|
| 404 |
+
# 2. nonwrappers
|
| 405 |
+
# 3. tryfirst nonwrappers
|
| 406 |
+
# 4. trylast wrappers
|
| 407 |
+
# 5. wrappers
|
| 408 |
+
# 6. tryfirst wrappers
|
| 409 |
+
self._hookimpls: Final[list[HookImpl]] = []
|
| 410 |
+
self._call_history: _CallHistory | None = None
|
| 411 |
+
# TODO: Document, or make private.
|
| 412 |
+
self.spec: HookSpec | None = None
|
| 413 |
+
if specmodule_or_class is not None:
|
| 414 |
+
assert spec_opts is not None
|
| 415 |
+
self.set_specification(specmodule_or_class, spec_opts)
|
| 416 |
+
|
| 417 |
+
# TODO: Document, or make private.
|
| 418 |
+
def has_spec(self) -> bool:
|
| 419 |
+
return self.spec is not None
|
| 420 |
+
|
| 421 |
+
# TODO: Document, or make private.
|
| 422 |
+
def set_specification(
|
| 423 |
+
self,
|
| 424 |
+
specmodule_or_class: _Namespace,
|
| 425 |
+
spec_opts: HookspecOpts,
|
| 426 |
+
) -> None:
|
| 427 |
+
if self.spec is not None:
|
| 428 |
+
raise ValueError(
|
| 429 |
+
f"Hook {self.spec.name!r} is already registered "
|
| 430 |
+
f"within namespace {self.spec.namespace}"
|
| 431 |
+
)
|
| 432 |
+
self.spec = HookSpec(specmodule_or_class, self.name, spec_opts)
|
| 433 |
+
if spec_opts.get("historic"):
|
| 434 |
+
self._call_history = []
|
| 435 |
+
|
| 436 |
+
def is_historic(self) -> bool:
|
| 437 |
+
"""Whether this caller is :ref:`historic <historic>`."""
|
| 438 |
+
return self._call_history is not None
|
| 439 |
+
|
| 440 |
+
def _remove_plugin(self, plugin: _Plugin) -> None:
|
| 441 |
+
for i, method in enumerate(self._hookimpls):
|
| 442 |
+
if method.plugin == plugin:
|
| 443 |
+
del self._hookimpls[i]
|
| 444 |
+
return
|
| 445 |
+
raise ValueError(f"plugin {plugin!r} not found")
|
| 446 |
+
|
| 447 |
+
def get_hookimpls(self) -> list[HookImpl]:
|
| 448 |
+
"""Get all registered hook implementations for this hook."""
|
| 449 |
+
return self._hookimpls.copy()
|
| 450 |
+
|
| 451 |
+
def _add_hookimpl(self, hookimpl: HookImpl) -> None:
|
| 452 |
+
"""Add an implementation to the callback chain."""
|
| 453 |
+
for i, method in enumerate(self._hookimpls):
|
| 454 |
+
if method.hookwrapper or method.wrapper:
|
| 455 |
+
splitpoint = i
|
| 456 |
+
break
|
| 457 |
+
else:
|
| 458 |
+
splitpoint = len(self._hookimpls)
|
| 459 |
+
if hookimpl.hookwrapper or hookimpl.wrapper:
|
| 460 |
+
start, end = splitpoint, len(self._hookimpls)
|
| 461 |
+
else:
|
| 462 |
+
start, end = 0, splitpoint
|
| 463 |
+
|
| 464 |
+
if hookimpl.trylast:
|
| 465 |
+
self._hookimpls.insert(start, hookimpl)
|
| 466 |
+
elif hookimpl.tryfirst:
|
| 467 |
+
self._hookimpls.insert(end, hookimpl)
|
| 468 |
+
else:
|
| 469 |
+
# find last non-tryfirst method
|
| 470 |
+
i = end - 1
|
| 471 |
+
while i >= start and self._hookimpls[i].tryfirst:
|
| 472 |
+
i -= 1
|
| 473 |
+
self._hookimpls.insert(i + 1, hookimpl)
|
| 474 |
+
|
| 475 |
+
def __repr__(self) -> str:
|
| 476 |
+
return f"<HookCaller {self.name!r}>"
|
| 477 |
+
|
| 478 |
+
def _verify_all_args_are_provided(self, kwargs: Mapping[str, object]) -> None:
|
| 479 |
+
# This is written to avoid expensive operations when not needed.
|
| 480 |
+
if self.spec:
|
| 481 |
+
for argname in self.spec.argnames:
|
| 482 |
+
if argname not in kwargs:
|
| 483 |
+
notincall = ", ".join(
|
| 484 |
+
repr(argname)
|
| 485 |
+
for argname in self.spec.argnames
|
| 486 |
+
# Avoid self.spec.argnames - kwargs.keys()
|
| 487 |
+
# it doesn't preserve order.
|
| 488 |
+
if argname not in kwargs.keys()
|
| 489 |
+
)
|
| 490 |
+
warnings.warn(
|
| 491 |
+
f"Argument(s) {notincall} which are declared in the hookspec "
|
| 492 |
+
"cannot be found in this hook call",
|
| 493 |
+
stacklevel=2,
|
| 494 |
+
)
|
| 495 |
+
break
|
| 496 |
+
|
| 497 |
+
def __call__(self, **kwargs: object) -> Any:
|
| 498 |
+
"""Call the hook.
|
| 499 |
+
|
| 500 |
+
Only accepts keyword arguments, which should match the hook
|
| 501 |
+
specification.
|
| 502 |
+
|
| 503 |
+
Returns the result(s) of calling all registered plugins, see
|
| 504 |
+
:ref:`calling`.
|
| 505 |
+
"""
|
| 506 |
+
assert not self.is_historic(), (
|
| 507 |
+
"Cannot directly call a historic hook - use call_historic instead."
|
| 508 |
+
)
|
| 509 |
+
self._verify_all_args_are_provided(kwargs)
|
| 510 |
+
firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
|
| 511 |
+
# Copy because plugins may register other plugins during iteration (#438).
|
| 512 |
+
return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult)
|
| 513 |
+
|
| 514 |
+
def call_historic(
|
| 515 |
+
self,
|
| 516 |
+
result_callback: Callable[[Any], None] | None = None,
|
| 517 |
+
kwargs: Mapping[str, object] | None = None,
|
| 518 |
+
) -> None:
|
| 519 |
+
"""Call the hook with given ``kwargs`` for all registered plugins and
|
| 520 |
+
for all plugins which will be registered afterwards, see
|
| 521 |
+
:ref:`historic`.
|
| 522 |
+
|
| 523 |
+
:param result_callback:
|
| 524 |
+
If provided, will be called for each non-``None`` result obtained
|
| 525 |
+
from a hook implementation.
|
| 526 |
+
"""
|
| 527 |
+
assert self._call_history is not None
|
| 528 |
+
kwargs = kwargs or {}
|
| 529 |
+
self._verify_all_args_are_provided(kwargs)
|
| 530 |
+
self._call_history.append((kwargs, result_callback))
|
| 531 |
+
# Historizing hooks don't return results.
|
| 532 |
+
# Remember firstresult isn't compatible with historic.
|
| 533 |
+
# Copy because plugins may register other plugins during iteration (#438).
|
| 534 |
+
res = self._hookexec(self.name, self._hookimpls.copy(), kwargs, False)
|
| 535 |
+
if result_callback is None:
|
| 536 |
+
return
|
| 537 |
+
if isinstance(res, list):
|
| 538 |
+
for x in res:
|
| 539 |
+
result_callback(x)
|
| 540 |
+
|
| 541 |
+
def call_extra(
|
| 542 |
+
self, methods: Sequence[Callable[..., object]], kwargs: Mapping[str, object]
|
| 543 |
+
) -> Any:
|
| 544 |
+
"""Call the hook with some additional temporarily participating
|
| 545 |
+
methods using the specified ``kwargs`` as call parameters, see
|
| 546 |
+
:ref:`call_extra`."""
|
| 547 |
+
assert not self.is_historic(), (
|
| 548 |
+
"Cannot directly call a historic hook - use call_historic instead."
|
| 549 |
+
)
|
| 550 |
+
self._verify_all_args_are_provided(kwargs)
|
| 551 |
+
opts: HookimplOpts = {
|
| 552 |
+
"wrapper": False,
|
| 553 |
+
"hookwrapper": False,
|
| 554 |
+
"optionalhook": False,
|
| 555 |
+
"trylast": False,
|
| 556 |
+
"tryfirst": False,
|
| 557 |
+
"specname": None,
|
| 558 |
+
}
|
| 559 |
+
hookimpls = self._hookimpls.copy()
|
| 560 |
+
for method in methods:
|
| 561 |
+
hookimpl = HookImpl(None, "<temp>", method, opts)
|
| 562 |
+
# Find last non-tryfirst nonwrapper method.
|
| 563 |
+
i = len(hookimpls) - 1
|
| 564 |
+
while i >= 0 and (
|
| 565 |
+
# Skip wrappers.
|
| 566 |
+
(hookimpls[i].hookwrapper or hookimpls[i].wrapper)
|
| 567 |
+
# Skip tryfirst nonwrappers.
|
| 568 |
+
or hookimpls[i].tryfirst
|
| 569 |
+
):
|
| 570 |
+
i -= 1
|
| 571 |
+
hookimpls.insert(i + 1, hookimpl)
|
| 572 |
+
firstresult = self.spec.opts.get("firstresult", False) if self.spec else False
|
| 573 |
+
return self._hookexec(self.name, hookimpls, kwargs, firstresult)
|
| 574 |
+
|
| 575 |
+
def _maybe_apply_history(self, method: HookImpl) -> None:
|
| 576 |
+
"""Apply call history to a new hookimpl if it is marked as historic."""
|
| 577 |
+
if self.is_historic():
|
| 578 |
+
assert self._call_history is not None
|
| 579 |
+
for kwargs, result_callback in self._call_history:
|
| 580 |
+
res = self._hookexec(self.name, [method], kwargs, False)
|
| 581 |
+
if res and result_callback is not None:
|
| 582 |
+
# XXX: remember firstresult isn't compat with historic
|
| 583 |
+
assert isinstance(res, list)
|
| 584 |
+
result_callback(res[0])
|
| 585 |
+
|
| 586 |
+
|
| 587 |
+
# Historical name (pluggy<=1.2), kept for backward compatibility.
|
| 588 |
+
_HookCaller = HookCaller
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
class _SubsetHookCaller(HookCaller):
|
| 592 |
+
"""A proxy to another HookCaller which manages calls to all registered
|
| 593 |
+
plugins except the ones from remove_plugins."""
|
| 594 |
+
|
| 595 |
+
# This class is unusual: in inhertits from `HookCaller` so all of
|
| 596 |
+
# the *code* runs in the class, but it delegates all underlying *data*
|
| 597 |
+
# to the original HookCaller.
|
| 598 |
+
# `subset_hook_caller` used to be implemented by creating a full-fledged
|
| 599 |
+
# HookCaller, copying all hookimpls from the original. This had problems
|
| 600 |
+
# with memory leaks (#346) and historic calls (#347), which make a proxy
|
| 601 |
+
# approach better.
|
| 602 |
+
# An alternative implementation is to use a `_getattr__`/`__getattribute__`
|
| 603 |
+
# proxy, however that adds more overhead and is more tricky to implement.
|
| 604 |
+
|
| 605 |
+
__slots__ = (
|
| 606 |
+
"_orig",
|
| 607 |
+
"_remove_plugins",
|
| 608 |
+
)
|
| 609 |
+
|
| 610 |
+
def __init__(self, orig: HookCaller, remove_plugins: Set[_Plugin]) -> None:
|
| 611 |
+
self._orig = orig
|
| 612 |
+
self._remove_plugins = remove_plugins
|
| 613 |
+
self.name = orig.name # type: ignore[misc]
|
| 614 |
+
self._hookexec = orig._hookexec # type: ignore[misc]
|
| 615 |
+
|
| 616 |
+
@property # type: ignore[misc]
|
| 617 |
+
def _hookimpls(self) -> list[HookImpl]:
|
| 618 |
+
return [
|
| 619 |
+
impl
|
| 620 |
+
for impl in self._orig._hookimpls
|
| 621 |
+
if impl.plugin not in self._remove_plugins
|
| 622 |
+
]
|
| 623 |
+
|
| 624 |
+
@property
|
| 625 |
+
def spec(self) -> HookSpec | None: # type: ignore[override]
|
| 626 |
+
return self._orig.spec
|
| 627 |
+
|
| 628 |
+
@property
|
| 629 |
+
def _call_history(self) -> _CallHistory | None: # type: ignore[override]
|
| 630 |
+
return self._orig._call_history
|
| 631 |
+
|
| 632 |
+
def __repr__(self) -> str:
|
| 633 |
+
return f"<_SubsetHookCaller {self.name!r}>"
|
| 634 |
+
|
| 635 |
+
|
| 636 |
+
@final
|
| 637 |
+
class HookImpl:
|
| 638 |
+
"""A hook implementation in a :class:`HookCaller`."""
|
| 639 |
+
|
| 640 |
+
__slots__ = (
|
| 641 |
+
"function",
|
| 642 |
+
"argnames",
|
| 643 |
+
"kwargnames",
|
| 644 |
+
"plugin",
|
| 645 |
+
"opts",
|
| 646 |
+
"plugin_name",
|
| 647 |
+
"wrapper",
|
| 648 |
+
"hookwrapper",
|
| 649 |
+
"optionalhook",
|
| 650 |
+
"tryfirst",
|
| 651 |
+
"trylast",
|
| 652 |
+
)
|
| 653 |
+
|
| 654 |
+
def __init__(
|
| 655 |
+
self,
|
| 656 |
+
plugin: _Plugin,
|
| 657 |
+
plugin_name: str,
|
| 658 |
+
function: _HookImplFunction[object],
|
| 659 |
+
hook_impl_opts: HookimplOpts,
|
| 660 |
+
) -> None:
|
| 661 |
+
""":meta private:"""
|
| 662 |
+
#: The hook implementation function.
|
| 663 |
+
self.function: Final = function
|
| 664 |
+
argnames, kwargnames = varnames(self.function)
|
| 665 |
+
#: The positional parameter names of ``function```.
|
| 666 |
+
self.argnames: Final = argnames
|
| 667 |
+
#: The keyword parameter names of ``function```.
|
| 668 |
+
self.kwargnames: Final = kwargnames
|
| 669 |
+
#: The plugin which defined this hook implementation.
|
| 670 |
+
self.plugin: Final = plugin
|
| 671 |
+
#: The :class:`HookimplOpts` used to configure this hook implementation.
|
| 672 |
+
self.opts: Final = hook_impl_opts
|
| 673 |
+
#: The name of the plugin which defined this hook implementation.
|
| 674 |
+
self.plugin_name: Final = plugin_name
|
| 675 |
+
#: Whether the hook implementation is a :ref:`wrapper <hookwrapper>`.
|
| 676 |
+
self.wrapper: Final = hook_impl_opts["wrapper"]
|
| 677 |
+
#: Whether the hook implementation is an :ref:`old-style wrapper
|
| 678 |
+
#: <old_style_hookwrappers>`.
|
| 679 |
+
self.hookwrapper: Final = hook_impl_opts["hookwrapper"]
|
| 680 |
+
#: Whether validation against a hook specification is :ref:`optional
|
| 681 |
+
#: <optionalhook>`.
|
| 682 |
+
self.optionalhook: Final = hook_impl_opts["optionalhook"]
|
| 683 |
+
#: Whether to try to order this hook implementation :ref:`first
|
| 684 |
+
#: <callorder>`.
|
| 685 |
+
self.tryfirst: Final = hook_impl_opts["tryfirst"]
|
| 686 |
+
#: Whether to try to order this hook implementation :ref:`last
|
| 687 |
+
#: <callorder>`.
|
| 688 |
+
self.trylast: Final = hook_impl_opts["trylast"]
|
| 689 |
+
|
| 690 |
+
def __repr__(self) -> str:
|
| 691 |
+
return f"<HookImpl plugin_name={self.plugin_name!r}, plugin={self.plugin!r}>"
|
| 692 |
+
|
| 693 |
+
|
| 694 |
+
@final
|
| 695 |
+
class HookSpec:
|
| 696 |
+
__slots__ = (
|
| 697 |
+
"namespace",
|
| 698 |
+
"function",
|
| 699 |
+
"name",
|
| 700 |
+
"argnames",
|
| 701 |
+
"kwargnames",
|
| 702 |
+
"opts",
|
| 703 |
+
"warn_on_impl",
|
| 704 |
+
"warn_on_impl_args",
|
| 705 |
+
)
|
| 706 |
+
|
| 707 |
+
def __init__(self, namespace: _Namespace, name: str, opts: HookspecOpts) -> None:
|
| 708 |
+
self.namespace = namespace
|
| 709 |
+
self.function: Callable[..., object] = getattr(namespace, name)
|
| 710 |
+
self.name = name
|
| 711 |
+
self.argnames, self.kwargnames = varnames(self.function)
|
| 712 |
+
self.opts = opts
|
| 713 |
+
self.warn_on_impl = opts.get("warn_on_impl")
|
| 714 |
+
self.warn_on_impl_args = opts.get("warn_on_impl_args")
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_manager.py
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from collections.abc import Iterable
|
| 4 |
+
from collections.abc import Mapping
|
| 5 |
+
from collections.abc import Sequence
|
| 6 |
+
import inspect
|
| 7 |
+
import types
|
| 8 |
+
from typing import Any
|
| 9 |
+
from typing import Callable
|
| 10 |
+
from typing import cast
|
| 11 |
+
from typing import Final
|
| 12 |
+
from typing import TYPE_CHECKING
|
| 13 |
+
import warnings
|
| 14 |
+
|
| 15 |
+
from . import _tracing
|
| 16 |
+
from ._callers import _multicall
|
| 17 |
+
from ._hooks import _HookImplFunction
|
| 18 |
+
from ._hooks import _Namespace
|
| 19 |
+
from ._hooks import _Plugin
|
| 20 |
+
from ._hooks import _SubsetHookCaller
|
| 21 |
+
from ._hooks import HookCaller
|
| 22 |
+
from ._hooks import HookImpl
|
| 23 |
+
from ._hooks import HookimplOpts
|
| 24 |
+
from ._hooks import HookRelay
|
| 25 |
+
from ._hooks import HookspecOpts
|
| 26 |
+
from ._hooks import normalize_hookimpl_opts
|
| 27 |
+
from ._result import Result
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
if TYPE_CHECKING:
|
| 31 |
+
# importtlib.metadata import is slow, defer it.
|
| 32 |
+
import importlib.metadata
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
_BeforeTrace = Callable[[str, Sequence[HookImpl], Mapping[str, Any]], None]
|
| 36 |
+
_AfterTrace = Callable[[Result[Any], str, Sequence[HookImpl], Mapping[str, Any]], None]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _warn_for_function(warning: Warning, function: Callable[..., object]) -> None:
|
| 40 |
+
func = cast(types.FunctionType, function)
|
| 41 |
+
warnings.warn_explicit(
|
| 42 |
+
warning,
|
| 43 |
+
type(warning),
|
| 44 |
+
lineno=func.__code__.co_firstlineno,
|
| 45 |
+
filename=func.__code__.co_filename,
|
| 46 |
+
)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class PluginValidationError(Exception):
|
| 50 |
+
"""Plugin failed validation.
|
| 51 |
+
|
| 52 |
+
:param plugin: The plugin which failed validation.
|
| 53 |
+
:param message: Error message.
|
| 54 |
+
"""
|
| 55 |
+
|
| 56 |
+
def __init__(self, plugin: _Plugin, message: str) -> None:
|
| 57 |
+
super().__init__(message)
|
| 58 |
+
#: The plugin which failed validation.
|
| 59 |
+
self.plugin = plugin
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
class DistFacade:
|
| 63 |
+
"""Emulate a pkg_resources Distribution"""
|
| 64 |
+
|
| 65 |
+
def __init__(self, dist: importlib.metadata.Distribution) -> None:
|
| 66 |
+
self._dist = dist
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def project_name(self) -> str:
|
| 70 |
+
name: str = self.metadata["name"]
|
| 71 |
+
return name
|
| 72 |
+
|
| 73 |
+
def __getattr__(self, attr: str, default: Any | None = None) -> Any:
|
| 74 |
+
return getattr(self._dist, attr, default)
|
| 75 |
+
|
| 76 |
+
def __dir__(self) -> list[str]:
|
| 77 |
+
return sorted(dir(self._dist) + ["_dist", "project_name"])
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class PluginManager:
|
| 81 |
+
"""Core class which manages registration of plugin objects and 1:N hook
|
| 82 |
+
calling.
|
| 83 |
+
|
| 84 |
+
You can register new hooks by calling :meth:`add_hookspecs(module_or_class)
|
| 85 |
+
<PluginManager.add_hookspecs>`.
|
| 86 |
+
|
| 87 |
+
You can register plugin objects (which contain hook implementations) by
|
| 88 |
+
calling :meth:`register(plugin) <PluginManager.register>`.
|
| 89 |
+
|
| 90 |
+
For debugging purposes you can call :meth:`PluginManager.enable_tracing`
|
| 91 |
+
which will subsequently send debug information to the trace helper.
|
| 92 |
+
|
| 93 |
+
:param project_name:
|
| 94 |
+
The short project name. Prefer snake case. Make sure it's unique!
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
def __init__(self, project_name: str) -> None:
|
| 98 |
+
#: The project name.
|
| 99 |
+
self.project_name: Final = project_name
|
| 100 |
+
self._name2plugin: Final[dict[str, _Plugin]] = {}
|
| 101 |
+
self._plugin_distinfo: Final[list[tuple[_Plugin, DistFacade]]] = []
|
| 102 |
+
#: The "hook relay", used to call a hook on all registered plugins.
|
| 103 |
+
#: See :ref:`calling`.
|
| 104 |
+
self.hook: Final = HookRelay()
|
| 105 |
+
#: The tracing entry point. See :ref:`tracing`.
|
| 106 |
+
self.trace: Final[_tracing.TagTracerSub] = _tracing.TagTracer().get(
|
| 107 |
+
"pluginmanage"
|
| 108 |
+
)
|
| 109 |
+
self._inner_hookexec = _multicall
|
| 110 |
+
|
| 111 |
+
def _hookexec(
|
| 112 |
+
self,
|
| 113 |
+
hook_name: str,
|
| 114 |
+
methods: Sequence[HookImpl],
|
| 115 |
+
kwargs: Mapping[str, object],
|
| 116 |
+
firstresult: bool,
|
| 117 |
+
) -> object | list[object]:
|
| 118 |
+
# called from all hookcaller instances.
|
| 119 |
+
# enable_tracing will set its own wrapping function at self._inner_hookexec
|
| 120 |
+
return self._inner_hookexec(hook_name, methods, kwargs, firstresult)
|
| 121 |
+
|
| 122 |
+
def register(self, plugin: _Plugin, name: str | None = None) -> str | None:
|
| 123 |
+
"""Register a plugin and return its name.
|
| 124 |
+
|
| 125 |
+
:param name:
|
| 126 |
+
The name under which to register the plugin. If not specified, a
|
| 127 |
+
name is generated using :func:`get_canonical_name`.
|
| 128 |
+
|
| 129 |
+
:returns:
|
| 130 |
+
The plugin name. If the name is blocked from registering, returns
|
| 131 |
+
``None``.
|
| 132 |
+
|
| 133 |
+
If the plugin is already registered, raises a :exc:`ValueError`.
|
| 134 |
+
"""
|
| 135 |
+
plugin_name = name or self.get_canonical_name(plugin)
|
| 136 |
+
|
| 137 |
+
if plugin_name in self._name2plugin:
|
| 138 |
+
if self._name2plugin.get(plugin_name, -1) is None:
|
| 139 |
+
return None # blocked plugin, return None to indicate no registration
|
| 140 |
+
raise ValueError(
|
| 141 |
+
"Plugin name already registered: "
|
| 142 |
+
f"{plugin_name}={plugin}\n{self._name2plugin}"
|
| 143 |
+
)
|
| 144 |
+
|
| 145 |
+
if plugin in self._name2plugin.values():
|
| 146 |
+
raise ValueError(
|
| 147 |
+
"Plugin already registered under a different name: "
|
| 148 |
+
f"{plugin_name}={plugin}\n{self._name2plugin}"
|
| 149 |
+
)
|
| 150 |
+
|
| 151 |
+
# XXX if an error happens we should make sure no state has been
|
| 152 |
+
# changed at point of return
|
| 153 |
+
self._name2plugin[plugin_name] = plugin
|
| 154 |
+
|
| 155 |
+
# register matching hook implementations of the plugin
|
| 156 |
+
for name in dir(plugin):
|
| 157 |
+
hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
|
| 158 |
+
if hookimpl_opts is not None:
|
| 159 |
+
normalize_hookimpl_opts(hookimpl_opts)
|
| 160 |
+
method: _HookImplFunction[object] = getattr(plugin, name)
|
| 161 |
+
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
|
| 162 |
+
name = hookimpl_opts.get("specname") or name
|
| 163 |
+
hook: HookCaller | None = getattr(self.hook, name, None)
|
| 164 |
+
if hook is None:
|
| 165 |
+
hook = HookCaller(name, self._hookexec)
|
| 166 |
+
setattr(self.hook, name, hook)
|
| 167 |
+
elif hook.has_spec():
|
| 168 |
+
self._verify_hook(hook, hookimpl)
|
| 169 |
+
hook._maybe_apply_history(hookimpl)
|
| 170 |
+
hook._add_hookimpl(hookimpl)
|
| 171 |
+
return plugin_name
|
| 172 |
+
|
| 173 |
+
def parse_hookimpl_opts(self, plugin: _Plugin, name: str) -> HookimplOpts | None:
|
| 174 |
+
"""Try to obtain a hook implementation from an item with the given name
|
| 175 |
+
in the given plugin which is being searched for hook impls.
|
| 176 |
+
|
| 177 |
+
:returns:
|
| 178 |
+
The parsed hookimpl options, or None to skip the given item.
|
| 179 |
+
|
| 180 |
+
This method can be overridden by ``PluginManager`` subclasses to
|
| 181 |
+
customize how hook implementation are picked up. By default, returns the
|
| 182 |
+
options for items decorated with :class:`HookimplMarker`.
|
| 183 |
+
"""
|
| 184 |
+
method: object = getattr(plugin, name)
|
| 185 |
+
if not inspect.isroutine(method):
|
| 186 |
+
return None
|
| 187 |
+
try:
|
| 188 |
+
res: HookimplOpts | None = getattr(
|
| 189 |
+
method, self.project_name + "_impl", None
|
| 190 |
+
)
|
| 191 |
+
except Exception: # pragma: no cover
|
| 192 |
+
res = {} # type: ignore[assignment] #pragma: no cover
|
| 193 |
+
if res is not None and not isinstance(res, dict):
|
| 194 |
+
# false positive
|
| 195 |
+
res = None # type:ignore[unreachable] #pragma: no cover
|
| 196 |
+
return res
|
| 197 |
+
|
| 198 |
+
def unregister(
|
| 199 |
+
self, plugin: _Plugin | None = None, name: str | None = None
|
| 200 |
+
) -> Any | None:
|
| 201 |
+
"""Unregister a plugin and all of its hook implementations.
|
| 202 |
+
|
| 203 |
+
The plugin can be specified either by the plugin object or the plugin
|
| 204 |
+
name. If both are specified, they must agree.
|
| 205 |
+
|
| 206 |
+
Returns the unregistered plugin, or ``None`` if not found.
|
| 207 |
+
"""
|
| 208 |
+
if name is None:
|
| 209 |
+
assert plugin is not None, "one of name or plugin needs to be specified"
|
| 210 |
+
name = self.get_name(plugin)
|
| 211 |
+
assert name is not None, "plugin is not registered"
|
| 212 |
+
|
| 213 |
+
if plugin is None:
|
| 214 |
+
plugin = self.get_plugin(name)
|
| 215 |
+
if plugin is None:
|
| 216 |
+
return None
|
| 217 |
+
|
| 218 |
+
hookcallers = self.get_hookcallers(plugin)
|
| 219 |
+
if hookcallers:
|
| 220 |
+
for hookcaller in hookcallers:
|
| 221 |
+
hookcaller._remove_plugin(plugin)
|
| 222 |
+
|
| 223 |
+
# if self._name2plugin[name] == None registration was blocked: ignore
|
| 224 |
+
if self._name2plugin.get(name):
|
| 225 |
+
assert name is not None
|
| 226 |
+
del self._name2plugin[name]
|
| 227 |
+
|
| 228 |
+
return plugin
|
| 229 |
+
|
| 230 |
+
def set_blocked(self, name: str) -> None:
|
| 231 |
+
"""Block registrations of the given name, unregister if already registered."""
|
| 232 |
+
self.unregister(name=name)
|
| 233 |
+
self._name2plugin[name] = None
|
| 234 |
+
|
| 235 |
+
def is_blocked(self, name: str) -> bool:
|
| 236 |
+
"""Return whether the given plugin name is blocked."""
|
| 237 |
+
return name in self._name2plugin and self._name2plugin[name] is None
|
| 238 |
+
|
| 239 |
+
def unblock(self, name: str) -> bool:
|
| 240 |
+
"""Unblocks a name.
|
| 241 |
+
|
| 242 |
+
Returns whether the name was actually blocked.
|
| 243 |
+
"""
|
| 244 |
+
if self._name2plugin.get(name, -1) is None:
|
| 245 |
+
del self._name2plugin[name]
|
| 246 |
+
return True
|
| 247 |
+
return False
|
| 248 |
+
|
| 249 |
+
def add_hookspecs(self, module_or_class: _Namespace) -> None:
|
| 250 |
+
"""Add new hook specifications defined in the given ``module_or_class``.
|
| 251 |
+
|
| 252 |
+
Functions are recognized as hook specifications if they have been
|
| 253 |
+
decorated with a matching :class:`HookspecMarker`.
|
| 254 |
+
"""
|
| 255 |
+
names = []
|
| 256 |
+
for name in dir(module_or_class):
|
| 257 |
+
spec_opts = self.parse_hookspec_opts(module_or_class, name)
|
| 258 |
+
if spec_opts is not None:
|
| 259 |
+
hc: HookCaller | None = getattr(self.hook, name, None)
|
| 260 |
+
if hc is None:
|
| 261 |
+
hc = HookCaller(name, self._hookexec, module_or_class, spec_opts)
|
| 262 |
+
setattr(self.hook, name, hc)
|
| 263 |
+
else:
|
| 264 |
+
# Plugins registered this hook without knowing the spec.
|
| 265 |
+
hc.set_specification(module_or_class, spec_opts)
|
| 266 |
+
for hookfunction in hc.get_hookimpls():
|
| 267 |
+
self._verify_hook(hc, hookfunction)
|
| 268 |
+
names.append(name)
|
| 269 |
+
|
| 270 |
+
if not names:
|
| 271 |
+
raise ValueError(
|
| 272 |
+
f"did not find any {self.project_name!r} hooks in {module_or_class!r}"
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
def parse_hookspec_opts(
|
| 276 |
+
self, module_or_class: _Namespace, name: str
|
| 277 |
+
) -> HookspecOpts | None:
|
| 278 |
+
"""Try to obtain a hook specification from an item with the given name
|
| 279 |
+
in the given module or class which is being searched for hook specs.
|
| 280 |
+
|
| 281 |
+
:returns:
|
| 282 |
+
The parsed hookspec options for defining a hook, or None to skip the
|
| 283 |
+
given item.
|
| 284 |
+
|
| 285 |
+
This method can be overridden by ``PluginManager`` subclasses to
|
| 286 |
+
customize how hook specifications are picked up. By default, returns the
|
| 287 |
+
options for items decorated with :class:`HookspecMarker`.
|
| 288 |
+
"""
|
| 289 |
+
method = getattr(module_or_class, name)
|
| 290 |
+
opts: HookspecOpts | None = getattr(method, self.project_name + "_spec", None)
|
| 291 |
+
return opts
|
| 292 |
+
|
| 293 |
+
def get_plugins(self) -> set[Any]:
|
| 294 |
+
"""Return a set of all registered plugin objects."""
|
| 295 |
+
return {x for x in self._name2plugin.values() if x is not None}
|
| 296 |
+
|
| 297 |
+
def is_registered(self, plugin: _Plugin) -> bool:
|
| 298 |
+
"""Return whether the plugin is already registered."""
|
| 299 |
+
return any(plugin == val for val in self._name2plugin.values())
|
| 300 |
+
|
| 301 |
+
def get_canonical_name(self, plugin: _Plugin) -> str:
|
| 302 |
+
"""Return a canonical name for a plugin object.
|
| 303 |
+
|
| 304 |
+
Note that a plugin may be registered under a different name
|
| 305 |
+
specified by the caller of :meth:`register(plugin, name) <register>`.
|
| 306 |
+
To obtain the name of a registered plugin use :meth:`get_name(plugin)
|
| 307 |
+
<get_name>` instead.
|
| 308 |
+
"""
|
| 309 |
+
name: str | None = getattr(plugin, "__name__", None)
|
| 310 |
+
return name or str(id(plugin))
|
| 311 |
+
|
| 312 |
+
def get_plugin(self, name: str) -> Any | None:
|
| 313 |
+
"""Return the plugin registered under the given name, if any."""
|
| 314 |
+
return self._name2plugin.get(name)
|
| 315 |
+
|
| 316 |
+
def has_plugin(self, name: str) -> bool:
|
| 317 |
+
"""Return whether a plugin with the given name is registered."""
|
| 318 |
+
return self.get_plugin(name) is not None
|
| 319 |
+
|
| 320 |
+
def get_name(self, plugin: _Plugin) -> str | None:
|
| 321 |
+
"""Return the name the plugin is registered under, or ``None`` if
|
| 322 |
+
is isn't."""
|
| 323 |
+
for name, val in self._name2plugin.items():
|
| 324 |
+
if plugin == val:
|
| 325 |
+
return name
|
| 326 |
+
return None
|
| 327 |
+
|
| 328 |
+
def _verify_hook(self, hook: HookCaller, hookimpl: HookImpl) -> None:
|
| 329 |
+
if hook.is_historic() and (hookimpl.hookwrapper or hookimpl.wrapper):
|
| 330 |
+
raise PluginValidationError(
|
| 331 |
+
hookimpl.plugin,
|
| 332 |
+
f"Plugin {hookimpl.plugin_name!r}\nhook {hook.name!r}\n"
|
| 333 |
+
"historic incompatible with yield/wrapper/hookwrapper",
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
assert hook.spec is not None
|
| 337 |
+
if hook.spec.warn_on_impl:
|
| 338 |
+
_warn_for_function(hook.spec.warn_on_impl, hookimpl.function)
|
| 339 |
+
|
| 340 |
+
# positional arg checking
|
| 341 |
+
notinspec = set(hookimpl.argnames) - set(hook.spec.argnames)
|
| 342 |
+
if notinspec:
|
| 343 |
+
raise PluginValidationError(
|
| 344 |
+
hookimpl.plugin,
|
| 345 |
+
f"Plugin {hookimpl.plugin_name!r} for hook {hook.name!r}\n"
|
| 346 |
+
f"hookimpl definition: {_formatdef(hookimpl.function)}\n"
|
| 347 |
+
f"Argument(s) {notinspec} are declared in the hookimpl but "
|
| 348 |
+
"can not be found in the hookspec",
|
| 349 |
+
)
|
| 350 |
+
|
| 351 |
+
if hook.spec.warn_on_impl_args:
|
| 352 |
+
for hookimpl_argname in hookimpl.argnames:
|
| 353 |
+
argname_warning = hook.spec.warn_on_impl_args.get(hookimpl_argname)
|
| 354 |
+
if argname_warning is not None:
|
| 355 |
+
_warn_for_function(argname_warning, hookimpl.function)
|
| 356 |
+
|
| 357 |
+
if (
|
| 358 |
+
hookimpl.wrapper or hookimpl.hookwrapper
|
| 359 |
+
) and not inspect.isgeneratorfunction(hookimpl.function):
|
| 360 |
+
raise PluginValidationError(
|
| 361 |
+
hookimpl.plugin,
|
| 362 |
+
f"Plugin {hookimpl.plugin_name!r} for hook {hook.name!r}\n"
|
| 363 |
+
f"hookimpl definition: {_formatdef(hookimpl.function)}\n"
|
| 364 |
+
"Declared as wrapper=True or hookwrapper=True "
|
| 365 |
+
"but function is not a generator function",
|
| 366 |
+
)
|
| 367 |
+
|
| 368 |
+
if hookimpl.wrapper and hookimpl.hookwrapper:
|
| 369 |
+
raise PluginValidationError(
|
| 370 |
+
hookimpl.plugin,
|
| 371 |
+
f"Plugin {hookimpl.plugin_name!r} for hook {hook.name!r}\n"
|
| 372 |
+
f"hookimpl definition: {_formatdef(hookimpl.function)}\n"
|
| 373 |
+
"The wrapper=True and hookwrapper=True options are mutually exclusive",
|
| 374 |
+
)
|
| 375 |
+
|
| 376 |
+
def check_pending(self) -> None:
|
| 377 |
+
"""Verify that all hooks which have not been verified against a
|
| 378 |
+
hook specification are optional, otherwise raise
|
| 379 |
+
:exc:`PluginValidationError`."""
|
| 380 |
+
for name in self.hook.__dict__:
|
| 381 |
+
if name[0] == "_":
|
| 382 |
+
continue
|
| 383 |
+
hook: HookCaller = getattr(self.hook, name)
|
| 384 |
+
if not hook.has_spec():
|
| 385 |
+
for hookimpl in hook.get_hookimpls():
|
| 386 |
+
if not hookimpl.optionalhook:
|
| 387 |
+
raise PluginValidationError(
|
| 388 |
+
hookimpl.plugin,
|
| 389 |
+
f"unknown hook {name!r} in plugin {hookimpl.plugin!r}",
|
| 390 |
+
)
|
| 391 |
+
|
| 392 |
+
def load_setuptools_entrypoints(self, group: str, name: str | None = None) -> int:
|
| 393 |
+
"""Load modules from querying the specified setuptools ``group``.
|
| 394 |
+
|
| 395 |
+
:param group:
|
| 396 |
+
Entry point group to load plugins.
|
| 397 |
+
:param name:
|
| 398 |
+
If given, loads only plugins with the given ``name``.
|
| 399 |
+
|
| 400 |
+
:return:
|
| 401 |
+
The number of plugins loaded by this call.
|
| 402 |
+
"""
|
| 403 |
+
import importlib.metadata
|
| 404 |
+
|
| 405 |
+
count = 0
|
| 406 |
+
for dist in list(importlib.metadata.distributions()):
|
| 407 |
+
for ep in dist.entry_points:
|
| 408 |
+
if (
|
| 409 |
+
ep.group != group
|
| 410 |
+
or (name is not None and ep.name != name)
|
| 411 |
+
# already registered
|
| 412 |
+
or self.get_plugin(ep.name)
|
| 413 |
+
or self.is_blocked(ep.name)
|
| 414 |
+
):
|
| 415 |
+
continue
|
| 416 |
+
plugin = ep.load()
|
| 417 |
+
self.register(plugin, name=ep.name)
|
| 418 |
+
self._plugin_distinfo.append((plugin, DistFacade(dist)))
|
| 419 |
+
count += 1
|
| 420 |
+
return count
|
| 421 |
+
|
| 422 |
+
def list_plugin_distinfo(self) -> list[tuple[_Plugin, DistFacade]]:
|
| 423 |
+
"""Return a list of (plugin, distinfo) pairs for all
|
| 424 |
+
setuptools-registered plugins."""
|
| 425 |
+
return list(self._plugin_distinfo)
|
| 426 |
+
|
| 427 |
+
def list_name_plugin(self) -> list[tuple[str, _Plugin]]:
|
| 428 |
+
"""Return a list of (name, plugin) pairs for all registered plugins."""
|
| 429 |
+
return list(self._name2plugin.items())
|
| 430 |
+
|
| 431 |
+
def get_hookcallers(self, plugin: _Plugin) -> list[HookCaller] | None:
|
| 432 |
+
"""Get all hook callers for the specified plugin.
|
| 433 |
+
|
| 434 |
+
:returns:
|
| 435 |
+
The hook callers, or ``None`` if ``plugin`` is not registered in
|
| 436 |
+
this plugin manager.
|
| 437 |
+
"""
|
| 438 |
+
if self.get_name(plugin) is None:
|
| 439 |
+
return None
|
| 440 |
+
hookcallers = []
|
| 441 |
+
for hookcaller in self.hook.__dict__.values():
|
| 442 |
+
for hookimpl in hookcaller.get_hookimpls():
|
| 443 |
+
if hookimpl.plugin is plugin:
|
| 444 |
+
hookcallers.append(hookcaller)
|
| 445 |
+
return hookcallers
|
| 446 |
+
|
| 447 |
+
def add_hookcall_monitoring(
|
| 448 |
+
self, before: _BeforeTrace, after: _AfterTrace
|
| 449 |
+
) -> Callable[[], None]:
|
| 450 |
+
"""Add before/after tracing functions for all hooks.
|
| 451 |
+
|
| 452 |
+
Returns an undo function which, when called, removes the added tracers.
|
| 453 |
+
|
| 454 |
+
``before(hook_name, hook_impls, kwargs)`` will be called ahead
|
| 455 |
+
of all hook calls and receive a hookcaller instance, a list
|
| 456 |
+
of HookImpl instances and the keyword arguments for the hook call.
|
| 457 |
+
|
| 458 |
+
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
|
| 459 |
+
same arguments as ``before`` but also a :class:`~pluggy.Result` object
|
| 460 |
+
which represents the result of the overall hook call.
|
| 461 |
+
"""
|
| 462 |
+
oldcall = self._inner_hookexec
|
| 463 |
+
|
| 464 |
+
def traced_hookexec(
|
| 465 |
+
hook_name: str,
|
| 466 |
+
hook_impls: Sequence[HookImpl],
|
| 467 |
+
caller_kwargs: Mapping[str, object],
|
| 468 |
+
firstresult: bool,
|
| 469 |
+
) -> object | list[object]:
|
| 470 |
+
before(hook_name, hook_impls, caller_kwargs)
|
| 471 |
+
outcome = Result.from_call(
|
| 472 |
+
lambda: oldcall(hook_name, hook_impls, caller_kwargs, firstresult)
|
| 473 |
+
)
|
| 474 |
+
after(outcome, hook_name, hook_impls, caller_kwargs)
|
| 475 |
+
return outcome.get_result()
|
| 476 |
+
|
| 477 |
+
self._inner_hookexec = traced_hookexec
|
| 478 |
+
|
| 479 |
+
def undo() -> None:
|
| 480 |
+
self._inner_hookexec = oldcall
|
| 481 |
+
|
| 482 |
+
return undo
|
| 483 |
+
|
| 484 |
+
def enable_tracing(self) -> Callable[[], None]:
|
| 485 |
+
"""Enable tracing of hook calls.
|
| 486 |
+
|
| 487 |
+
Returns an undo function which, when called, removes the added tracing.
|
| 488 |
+
"""
|
| 489 |
+
hooktrace = self.trace.root.get("hook")
|
| 490 |
+
|
| 491 |
+
def before(
|
| 492 |
+
hook_name: str, methods: Sequence[HookImpl], kwargs: Mapping[str, object]
|
| 493 |
+
) -> None:
|
| 494 |
+
hooktrace.root.indent += 1
|
| 495 |
+
hooktrace(hook_name, kwargs)
|
| 496 |
+
|
| 497 |
+
def after(
|
| 498 |
+
outcome: Result[object],
|
| 499 |
+
hook_name: str,
|
| 500 |
+
methods: Sequence[HookImpl],
|
| 501 |
+
kwargs: Mapping[str, object],
|
| 502 |
+
) -> None:
|
| 503 |
+
if outcome.exception is None:
|
| 504 |
+
hooktrace("finish", hook_name, "-->", outcome.get_result())
|
| 505 |
+
hooktrace.root.indent -= 1
|
| 506 |
+
|
| 507 |
+
return self.add_hookcall_monitoring(before, after)
|
| 508 |
+
|
| 509 |
+
def subset_hook_caller(
|
| 510 |
+
self, name: str, remove_plugins: Iterable[_Plugin]
|
| 511 |
+
) -> HookCaller:
|
| 512 |
+
"""Return a proxy :class:`~pluggy.HookCaller` instance for the named
|
| 513 |
+
method which manages calls to all registered plugins except the ones
|
| 514 |
+
from remove_plugins."""
|
| 515 |
+
orig: HookCaller = getattr(self.hook, name)
|
| 516 |
+
plugins_to_remove = {plug for plug in remove_plugins if hasattr(plug, name)}
|
| 517 |
+
if plugins_to_remove:
|
| 518 |
+
return _SubsetHookCaller(orig, plugins_to_remove)
|
| 519 |
+
return orig
|
| 520 |
+
|
| 521 |
+
|
| 522 |
+
def _formatdef(func: Callable[..., object]) -> str:
|
| 523 |
+
return f"{func.__name__}{inspect.signature(func)}"
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_result.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hook wrapper "result" utilities.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
from types import TracebackType
|
| 8 |
+
from typing import Callable
|
| 9 |
+
from typing import cast
|
| 10 |
+
from typing import final
|
| 11 |
+
from typing import Generic
|
| 12 |
+
from typing import Optional
|
| 13 |
+
from typing import TypeVar
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
_ExcInfo = tuple[type[BaseException], BaseException, Optional[TracebackType]]
|
| 17 |
+
ResultType = TypeVar("ResultType")
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class HookCallError(Exception):
|
| 21 |
+
"""Hook was called incorrectly."""
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@final
|
| 25 |
+
class Result(Generic[ResultType]):
|
| 26 |
+
"""An object used to inspect and set the result in a :ref:`hook wrapper
|
| 27 |
+
<hookwrappers>`."""
|
| 28 |
+
|
| 29 |
+
__slots__ = ("_result", "_exception", "_traceback")
|
| 30 |
+
|
| 31 |
+
def __init__(
|
| 32 |
+
self,
|
| 33 |
+
result: ResultType | None,
|
| 34 |
+
exception: BaseException | None,
|
| 35 |
+
) -> None:
|
| 36 |
+
""":meta private:"""
|
| 37 |
+
self._result = result
|
| 38 |
+
self._exception = exception
|
| 39 |
+
# Exception __traceback__ is mutable, this keeps the original.
|
| 40 |
+
self._traceback = exception.__traceback__ if exception is not None else None
|
| 41 |
+
|
| 42 |
+
@property
|
| 43 |
+
def excinfo(self) -> _ExcInfo | None:
|
| 44 |
+
""":meta private:"""
|
| 45 |
+
exc = self._exception
|
| 46 |
+
if exc is None:
|
| 47 |
+
return None
|
| 48 |
+
else:
|
| 49 |
+
return (type(exc), exc, self._traceback)
|
| 50 |
+
|
| 51 |
+
@property
|
| 52 |
+
def exception(self) -> BaseException | None:
|
| 53 |
+
""":meta private:"""
|
| 54 |
+
return self._exception
|
| 55 |
+
|
| 56 |
+
@classmethod
|
| 57 |
+
def from_call(cls, func: Callable[[], ResultType]) -> Result[ResultType]:
|
| 58 |
+
""":meta private:"""
|
| 59 |
+
__tracebackhide__ = True
|
| 60 |
+
result = exception = None
|
| 61 |
+
try:
|
| 62 |
+
result = func()
|
| 63 |
+
except BaseException as exc:
|
| 64 |
+
exception = exc
|
| 65 |
+
return cls(result, exception)
|
| 66 |
+
|
| 67 |
+
def force_result(self, result: ResultType) -> None:
|
| 68 |
+
"""Force the result(s) to ``result``.
|
| 69 |
+
|
| 70 |
+
If the hook was marked as a ``firstresult`` a single value should
|
| 71 |
+
be set, otherwise set a (modified) list of results. Any exceptions
|
| 72 |
+
found during invocation will be deleted.
|
| 73 |
+
|
| 74 |
+
This overrides any previous result or exception.
|
| 75 |
+
"""
|
| 76 |
+
self._result = result
|
| 77 |
+
self._exception = None
|
| 78 |
+
self._traceback = None
|
| 79 |
+
|
| 80 |
+
def force_exception(self, exception: BaseException) -> None:
|
| 81 |
+
"""Force the result to fail with ``exception``.
|
| 82 |
+
|
| 83 |
+
This overrides any previous result or exception.
|
| 84 |
+
|
| 85 |
+
.. versionadded:: 1.1.0
|
| 86 |
+
"""
|
| 87 |
+
self._result = None
|
| 88 |
+
self._exception = exception
|
| 89 |
+
self._traceback = exception.__traceback__ if exception is not None else None
|
| 90 |
+
|
| 91 |
+
def get_result(self) -> ResultType:
|
| 92 |
+
"""Get the result(s) for this hook call.
|
| 93 |
+
|
| 94 |
+
If the hook was marked as a ``firstresult`` only a single value
|
| 95 |
+
will be returned, otherwise a list of results.
|
| 96 |
+
"""
|
| 97 |
+
__tracebackhide__ = True
|
| 98 |
+
exc = self._exception
|
| 99 |
+
tb = self._traceback
|
| 100 |
+
if exc is None:
|
| 101 |
+
return cast(ResultType, self._result)
|
| 102 |
+
else:
|
| 103 |
+
raise exc.with_traceback(tb)
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
# Historical name (pluggy<=1.2), kept for backward compatibility.
|
| 107 |
+
_Result = Result
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_tracing.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Tracing utils
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
+
from collections.abc import Sequence
|
| 8 |
+
from typing import Any
|
| 9 |
+
from typing import Callable
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
_Writer = Callable[[str], object]
|
| 13 |
+
_Processor = Callable[[tuple[str, ...], tuple[Any, ...]], object]
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class TagTracer:
|
| 17 |
+
def __init__(self) -> None:
|
| 18 |
+
self._tags2proc: dict[tuple[str, ...], _Processor] = {}
|
| 19 |
+
self._writer: _Writer | None = None
|
| 20 |
+
self.indent = 0
|
| 21 |
+
|
| 22 |
+
def get(self, name: str) -> TagTracerSub:
|
| 23 |
+
return TagTracerSub(self, (name,))
|
| 24 |
+
|
| 25 |
+
def _format_message(self, tags: Sequence[str], args: Sequence[object]) -> str:
|
| 26 |
+
if isinstance(args[-1], dict):
|
| 27 |
+
extra = args[-1]
|
| 28 |
+
args = args[:-1]
|
| 29 |
+
else:
|
| 30 |
+
extra = {}
|
| 31 |
+
|
| 32 |
+
content = " ".join(map(str, args))
|
| 33 |
+
indent = " " * self.indent
|
| 34 |
+
|
| 35 |
+
lines = ["{}{} [{}]\n".format(indent, content, ":".join(tags))]
|
| 36 |
+
|
| 37 |
+
for name, value in extra.items():
|
| 38 |
+
lines.append(f"{indent} {name}: {value}\n")
|
| 39 |
+
|
| 40 |
+
return "".join(lines)
|
| 41 |
+
|
| 42 |
+
def _processmessage(self, tags: tuple[str, ...], args: tuple[object, ...]) -> None:
|
| 43 |
+
if self._writer is not None and args:
|
| 44 |
+
self._writer(self._format_message(tags, args))
|
| 45 |
+
try:
|
| 46 |
+
processor = self._tags2proc[tags]
|
| 47 |
+
except KeyError:
|
| 48 |
+
pass
|
| 49 |
+
else:
|
| 50 |
+
processor(tags, args)
|
| 51 |
+
|
| 52 |
+
def setwriter(self, writer: _Writer | None) -> None:
|
| 53 |
+
self._writer = writer
|
| 54 |
+
|
| 55 |
+
def setprocessor(self, tags: str | tuple[str, ...], processor: _Processor) -> None:
|
| 56 |
+
if isinstance(tags, str):
|
| 57 |
+
tags = tuple(tags.split(":"))
|
| 58 |
+
else:
|
| 59 |
+
assert isinstance(tags, tuple)
|
| 60 |
+
self._tags2proc[tags] = processor
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class TagTracerSub:
|
| 64 |
+
def __init__(self, root: TagTracer, tags: tuple[str, ...]) -> None:
|
| 65 |
+
self.root = root
|
| 66 |
+
self.tags = tags
|
| 67 |
+
|
| 68 |
+
def __call__(self, *args: object) -> None:
|
| 69 |
+
self.root._processmessage(self.tags, args)
|
| 70 |
+
|
| 71 |
+
def get(self, name: str) -> TagTracerSub:
|
| 72 |
+
return self.__class__(self.root, self.tags + (name,))
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# file generated by setuptools-scm
|
| 2 |
+
# don't change, don't track in version control
|
| 3 |
+
|
| 4 |
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
| 5 |
+
|
| 6 |
+
TYPE_CHECKING = False
|
| 7 |
+
if TYPE_CHECKING:
|
| 8 |
+
from typing import Tuple
|
| 9 |
+
from typing import Union
|
| 10 |
+
|
| 11 |
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
| 12 |
+
else:
|
| 13 |
+
VERSION_TUPLE = object
|
| 14 |
+
|
| 15 |
+
version: str
|
| 16 |
+
__version__: str
|
| 17 |
+
__version_tuple__: VERSION_TUPLE
|
| 18 |
+
version_tuple: VERSION_TUPLE
|
| 19 |
+
|
| 20 |
+
__version__ = version = '1.6.0'
|
| 21 |
+
__version_tuple__ = version_tuple = (1, 6, 0)
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/_warnings.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import final
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class PluggyWarning(UserWarning):
|
| 5 |
+
"""Base class for all warnings emitted by pluggy."""
|
| 6 |
+
|
| 7 |
+
__module__ = "pluggy"
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
@final
|
| 11 |
+
class PluggyTeardownRaisedWarning(PluggyWarning):
|
| 12 |
+
"""A plugin raised an exception during an :ref:`old-style hookwrapper
|
| 13 |
+
<old_style_hookwrappers>` teardown.
|
| 14 |
+
|
| 15 |
+
Such exceptions are not handled by pluggy, and may cause subsequent
|
| 16 |
+
teardowns to be executed at unexpected times, or be skipped entirely.
|
| 17 |
+
|
| 18 |
+
This is an issue in the plugin implementation.
|
| 19 |
+
|
| 20 |
+
If the exception is unintended, fix the underlying cause.
|
| 21 |
+
|
| 22 |
+
If the exception is intended, switch to :ref:`new-style hook wrappers
|
| 23 |
+
<hookwrappers>`, or use :func:`result.force_exception()
|
| 24 |
+
<pluggy.Result.force_exception>` to set the exception instead of raising.
|
| 25 |
+
"""
|
| 26 |
+
|
| 27 |
+
__module__ = "pluggy"
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pluggy/py.typed
ADDED
|
File without changes
|
personalPromptBoost/.venv/lib/python3.13/site-packages/py.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# shim for pylib going away
|
| 2 |
+
# if pylib is installed this file will get skipped
|
| 3 |
+
# (`py/__init__.py` has higher precedence)
|
| 4 |
+
from __future__ import annotations
|
| 5 |
+
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
import _pytest._py.error as error
|
| 9 |
+
import _pytest._py.path as path
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
sys.modules["py.error"] = error
|
| 13 |
+
sys.modules["py.path"] = path
|
| 14 |
+
|
| 15 |
+
__all__ = ["error", "path"]
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/METADATA
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/RECORD
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pydantic-2.13.3.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 2 |
+
pydantic-2.13.3.dist-info/METADATA,sha256=dAqUziUUUKWugJ5oi9jnm5ZtqZ9nEZ8gIM45DV2917o,108770
|
| 3 |
+
pydantic-2.13.3.dist-info/RECORD,,
|
| 4 |
+
pydantic-2.13.3.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 5 |
+
pydantic-2.13.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
| 6 |
+
pydantic-2.13.3.dist-info/licenses/LICENSE,sha256=qeGG88oWte74QxjnpwFyE1GgDLe4rjpDlLZ7SeNSnvM,1129
|
| 7 |
+
pydantic/__init__.py,sha256=5iEnJ4wHv1OEzdKQPzaKaZKfO4pSQAC65ODrYI6_S8Y,15812
|
| 8 |
+
pydantic/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 9 |
+
pydantic/_internal/_config.py,sha256=Rzys1Joffn4JczElcYDqsZLRgBgHn2lYWqDR55oASPA,14839
|
| 10 |
+
pydantic/_internal/_core_metadata.py,sha256=Y_g2t3i7uluK-wXCZvzJfRFMPUM23aBYLfae4FzBPy0,5162
|
| 11 |
+
pydantic/_internal/_core_utils.py,sha256=1jru4VbJ0x63R6dtVcuOI-dKQTC_d_lSnJWEBQzGNEQ,6487
|
| 12 |
+
pydantic/_internal/_dataclasses.py,sha256=Zgqcm1WaJLBwTQQC5mGKNowjlTgX3mfX_J5e2vd24lM,13188
|
| 13 |
+
pydantic/_internal/_decorators.py,sha256=RDEG_Jau5NiJcfO0xgdT7EOgsU1LgWIYlX7wN5rYtVs,33620
|
| 14 |
+
pydantic/_internal/_decorators_v1.py,sha256=tfdfdpQKY4R2XCOwqHbZeoQMur6VNigRrfhudXBHx38,6185
|
| 15 |
+
pydantic/_internal/_discriminated_union.py,sha256=JLx_MVLep7Mxl1zbpdNZjvHDcz-J3OEW6WcdV184dcM,26255
|
| 16 |
+
pydantic/_internal/_docs_extraction.py,sha256=fyznSAHh5AzohnXZStV0HvH-nRbavNHPyg-knx-S_EE,4127
|
| 17 |
+
pydantic/_internal/_fields.py,sha256=hXeb-zodGwTDvG9OK1um18P64b7cqHb0GOngP4jrgcY,31557
|
| 18 |
+
pydantic/_internal/_forward_ref.py,sha256=5n3Y7-3AKLn8_FS3Yc7KutLiPUhyXmAtkEZOaFnonwM,611
|
| 19 |
+
pydantic/_internal/_generate_schema.py,sha256=lw4y1t5HKIDBosr9QgYtC7-GBu5y0g9qR7ZO3XUufBA,135560
|
| 20 |
+
pydantic/_internal/_generics.py,sha256=CXjcInlvci8VejaWn1f39kv0AcfJL0R523qRLFZVD-s,23393
|
| 21 |
+
pydantic/_internal/_git.py,sha256=IwPh3DPfa2Xq3rBuB9Nx8luR2A1i69QdeTfWWXIuCVg,809
|
| 22 |
+
pydantic/_internal/_import_utils.py,sha256=TRhxD5OuY6CUosioBdBcJUs0om7IIONiZdYAV7zQ8jM,402
|
| 23 |
+
pydantic/_internal/_internal_dataclass.py,sha256=_bedc1XbuuygRGiLZqkUkwwFpQaoR1hKLlR501nyySY,144
|
| 24 |
+
pydantic/_internal/_known_annotated_metadata.py,sha256=PynQIFQ61__4Gcrzn0D5ENllg7jPq_cxoLTmuFQBY88,16805
|
| 25 |
+
pydantic/_internal/_mock_val_ser.py,sha256=wmRRFSBvqfcLbI41PsFliB4u2AZ3mJpZeiERbD3xKTo,8885
|
| 26 |
+
pydantic/_internal/_model_construction.py,sha256=JoKmY4JrDBu3nG_tCIrJgtJJE1uq6v29TvTz5ElHE5g,38928
|
| 27 |
+
pydantic/_internal/_namespace_utils.py,sha256=hl3-TRAr82U2jTyPP3t-QqsvKLirxtkLfNfrN-fp0x8,12878
|
| 28 |
+
pydantic/_internal/_repr.py,sha256=jQfnJuyDxQpSRNhG29II9PX8e4Nv2qWZrEw2lqih3UE,5172
|
| 29 |
+
pydantic/_internal/_schema_gather.py,sha256=8nJ-uM6Y4z6xpasnGonEMubtNVX_mxeeRDFmd_qMVLA,9052
|
| 30 |
+
pydantic/_internal/_schema_generation_shared.py,sha256=F_rbQbrkoomgxsskdHpP0jUJ7TCfe0BADAEkq6CJ4nM,4842
|
| 31 |
+
pydantic/_internal/_serializers.py,sha256=YIWvSmAR5fnbGSWCOQduWt1yB4ZQY42eAruc-enrb6c,1491
|
| 32 |
+
pydantic/_internal/_signature.py,sha256=i_b6wtluiVWZRh1ZY8UvB2UZziP1KjqSXZgC-HxwOT0,6808
|
| 33 |
+
pydantic/_internal/_typing_extra.py,sha256=dDxqF46lzuqCoKLrAH_k95EDbayEeKb2lHjuTJ5OBoY,31574
|
| 34 |
+
pydantic/_internal/_utils.py,sha256=gN48BsR-FDrJDibCmo69ttQg67WbuFrdy_1NQL3cvLI,15959
|
| 35 |
+
pydantic/_internal/_validate_call.py,sha256=OD_BspHaL9FKzZ9XrndhiEuMnjF3SRIJUHtwv6yUffU,5366
|
| 36 |
+
pydantic/_internal/_validators.py,sha256=7GTjXXWFMLib4dxQ-HeaiHlAZiR2B2G8byCYMGrmQ48,20563
|
| 37 |
+
pydantic/_migration.py,sha256=VF73LRCUz3Irb5xVt13jb3NAcXVnEF6T1-J0OLfeZ5A,12160
|
| 38 |
+
pydantic/alias_generators.py,sha256=KM1n3u4JfLSBl1UuYg3hoYHzXJD-yvgrnq8u1ccwh_A,2124
|
| 39 |
+
pydantic/aliases.py,sha256=vhCHyoSWnX-EJ-wWb5qj4xyRssgGWnTQfzQp4GSZ9ug,4937
|
| 40 |
+
pydantic/annotated_handlers.py,sha256=WfyFSqwoEIFXBh7T73PycKloI1DiX45GWi0-JOsCR4Y,4407
|
| 41 |
+
pydantic/class_validators.py,sha256=i_V3j-PYdGLSLmj_IJZekTRjunO8SIVz8LMlquPyP7E,148
|
| 42 |
+
pydantic/color.py,sha256=AzqGfVQHF92_ZctDcue0DM4yTp2P6tekkwRINTWrLIo,21481
|
| 43 |
+
pydantic/config.py,sha256=o1P67FMWIQG-_RfGtKislkHwo4pXm_6jfsR41P88v78,44533
|
| 44 |
+
pydantic/dataclasses.py,sha256=4X9We0jj1KLwBtvYSkAFXNon46zrpBmRZANf4LfwbXg,18963
|
| 45 |
+
pydantic/datetime_parse.py,sha256=QC-WgMxMr_wQ_mNXUS7AVf-2hLEhvvsPY1PQyhSGOdk,150
|
| 46 |
+
pydantic/decorator.py,sha256=YX-jUApu5AKaVWKPoaV-n-4l7UbS69GEt9Ra3hszmKI,145
|
| 47 |
+
pydantic/deprecated/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 48 |
+
pydantic/deprecated/class_validators.py,sha256=n0jYQOcb5YQiw0b7YXyi7NPiYdV7ujWR4KyZumjTPok,10281
|
| 49 |
+
pydantic/deprecated/config.py,sha256=k_lsVk57paxLJOcBueH07cu1OgEgWdVBxm6lfaC3CCU,2663
|
| 50 |
+
pydantic/deprecated/copy_internals.py,sha256=Ghd-vkMd5EYCCgyCGtPKO58np9cEKBQC6qkBeIEFI2g,7618
|
| 51 |
+
pydantic/deprecated/decorator.py,sha256=TBm6bJ7wJsNih_8Wq5IzDcwP32m9_vfxs96desLuk00,10845
|
| 52 |
+
pydantic/deprecated/json.py,sha256=HlWCG35RRrxyzuTS6LTQiZBwRhmDZWmeqQH8rLW6wA8,4657
|
| 53 |
+
pydantic/deprecated/parse.py,sha256=Gzd6b_g8zJXcuE7QRq5adhx_EMJahXfcpXCF0RgrqqI,2511
|
| 54 |
+
pydantic/deprecated/tools.py,sha256=Nrm9oFRZWp8-jlfvPgJILEsywp4YzZD52XIGPDLxHcI,3330
|
| 55 |
+
pydantic/env_settings.py,sha256=6IHeeWEqlUPRUv3V-AXiF_W91fg2Jw_M3O0l34J_eyA,148
|
| 56 |
+
pydantic/error_wrappers.py,sha256=RK6mqATc9yMD-KBD9IJS9HpKCprWHd8wo84Bnm-3fR8,150
|
| 57 |
+
pydantic/errors.py,sha256=DrECPCWhSYrQ8Ba4O8hKzIAM2i9GBHTXWALzaFDpLf4,6013
|
| 58 |
+
pydantic/experimental/__init__.py,sha256=QT7rKYdDsCiTJ9GEjmsQdWHScwpKrrNkGq6vqONP6RQ,104
|
| 59 |
+
pydantic/experimental/arguments_schema.py,sha256=EFnjX_ulp-tPyUjQX5pmQtug1OFL_Acc8bcMbLd-fVY,1866
|
| 60 |
+
pydantic/experimental/missing_sentinel.py,sha256=hQejgtF00wUuQMni9429evg-eXyIwpKvjsD8ofqfj-w,127
|
| 61 |
+
pydantic/experimental/pipeline.py,sha256=auoW6l6g1FC41LciPmsI1M6ncf00Szde9B3C-yvb9mI,23956
|
| 62 |
+
pydantic/fields.py,sha256=a8ZhJfI8FD6TQDD7-cWMm1ZXlQva0wMdT2Ey5VvVe-M,82023
|
| 63 |
+
pydantic/functional_serializers.py,sha256=zwRAjZusORtEbtxpVU20kg8FpemyZz4Fq6wJk1mpwYQ,18117
|
| 64 |
+
pydantic/functional_validators.py,sha256=7p-4jvP__9jZyvcdXiide5pAa_JlM7fJ1BgkqbjxSWM,31724
|
| 65 |
+
pydantic/generics.py,sha256=0ZqZ9O9annIj_3mGBRqps4htey3b5lV1-d2tUxPMMnA,144
|
| 66 |
+
pydantic/json.py,sha256=ZH8RkI7h4Bz-zp8OdTAxbJUoVvcoU-jhMdRZ0B-k0xc,140
|
| 67 |
+
pydantic/json_schema.py,sha256=ZVJNgaKCcna-M_pcooDQAjFXKvxwO2maKHLKHrs1r3A,126176
|
| 68 |
+
pydantic/main.py,sha256=NbhCz-ku8wDgYLQMBi75Ov7hywdc8LewA3oUNIZ8JVQ,85334
|
| 69 |
+
pydantic/mypy.py,sha256=sYmmZrL_GvoYSLcBuPRRDfSfgQWehkzw_ZvNkwDY2ME,60971
|
| 70 |
+
pydantic/networks.py,sha256=gCB96gt0G7tiVDhVnJfpKr1ARL5qkH-SPZkuNkmG2O4,42102
|
| 71 |
+
pydantic/parse.py,sha256=wkd82dgtvWtD895U_I6E1htqMlGhBSYEV39cuBSeo3A,141
|
| 72 |
+
pydantic/plugin/__init__.py,sha256=a7Tw366U6K3kltCCNZY76nc9ss-7uGGQ40TXad9OypQ,7333
|
| 73 |
+
pydantic/plugin/_loader.py,sha256=hAjgSljoIhGx3AVpIpuqw5SPttBNNeGBSTrqSMnNiJk,2213
|
| 74 |
+
pydantic/plugin/_schema_validator.py,sha256=5M5Ic1bZnjhNDxtRDVKbRPTQ6po6QuKMY7MguMkHeW0,5445
|
| 75 |
+
pydantic/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 76 |
+
pydantic/root_model.py,sha256=cOMoeWdp536KF85uRIcW-oz1O7d5GRCpx9HTS4-1uf8,6394
|
| 77 |
+
pydantic/schema.py,sha256=Vqqjvq_LnapVknebUd3Bp_J1p2gXZZnZRgL48bVEG7o,142
|
| 78 |
+
pydantic/tools.py,sha256=iHQpd8SJ5DCTtPV5atAV06T89bjSaMFeZZ2LX9lasZY,141
|
| 79 |
+
pydantic/type_adapter.py,sha256=T05g8WQczBsVU_35RdKxgjIi7Y7LIip67pF3NI7X4GE,36123
|
| 80 |
+
pydantic/types.py,sha256=fpYcGnAncK4QjaFm3jZtqYwxJYzB7rJJPfAl89emzyQ,105961
|
| 81 |
+
pydantic/typing.py,sha256=P7feA35MwTcLsR1uL7db0S-oydBxobmXa55YDoBgajQ,138
|
| 82 |
+
pydantic/utils.py,sha256=15nR2QpqTBFlQV4TNtTItMyTJx_fbyV-gPmIEY1Gooc,141
|
| 83 |
+
pydantic/v1/__init__.py,sha256=SxQPklgBs4XHJwE6BZ9qoewYoGiNyYUnmHzEFCZbfnI,2946
|
| 84 |
+
pydantic/v1/_hypothesis_plugin.py,sha256=5ES5xWuw1FQAsymLezy8QgnVz0ZpVfU3jkmT74H27VQ,14847
|
| 85 |
+
pydantic/v1/annotated_types.py,sha256=uk2NAAxqiNELKjiHhyhxKaIOh8F1lYW_LzrW3X7oZBc,3157
|
| 86 |
+
pydantic/v1/class_validators.py,sha256=ULOaIUgYUDBsHL7EEVEarcM-UubKUggoN8hSbDonsFE,14672
|
| 87 |
+
pydantic/v1/color.py,sha256=iZABLYp6OVoo2AFkP9Ipri_wSc6-Kklu8YuhSartd5g,16844
|
| 88 |
+
pydantic/v1/config.py,sha256=a6P0Wer9x4cbwKW7Xv8poSUqM4WP-RLWwX6YMpYq9AA,6532
|
| 89 |
+
pydantic/v1/dataclasses.py,sha256=784cqvInbwIPWr9usfpX3ch7z4t3J2tTK6N067_wk1o,18172
|
| 90 |
+
pydantic/v1/datetime_parse.py,sha256=4Qy1kQpq3rNVZJeIHeSPDpuS2Bvhp1KPtzJG1xu-H00,7724
|
| 91 |
+
pydantic/v1/decorator.py,sha256=zaaxxxoWPCm818D1bs0yhapRjXm32V8G0ZHWCdM1uXA,10339
|
| 92 |
+
pydantic/v1/env_settings.py,sha256=A9VXwtRl02AY-jH0C0ouy5VNw3fi6F_pkzuHDjgAAOM,14105
|
| 93 |
+
pydantic/v1/error_wrappers.py,sha256=6625Mfw9qkC2NwitB_JFAWe8B-Xv6zBU7rL9k28tfyo,5196
|
| 94 |
+
pydantic/v1/errors.py,sha256=mIwPED5vGM5Q5v4C4Z1JPldTRH-omvEylH6ksMhOmPw,17726
|
| 95 |
+
pydantic/v1/fields.py,sha256=VqWJCriUNiEyptXroDVJ501JpVA0en2VANcksqXL2b8,50649
|
| 96 |
+
pydantic/v1/generics.py,sha256=YzyKTZN6x5Q1RGJ3WQ9jN-uwHJxL3W4qoZqwcZXqxWg,17829
|
| 97 |
+
pydantic/v1/json.py,sha256=WQ5Hy_hIpfdR3YS8k6N2E6KMJzsdbBi_ldWOPJaV81M,3390
|
| 98 |
+
pydantic/v1/main.py,sha256=vRB1TbpkzPN3P5ijJlc-cjNuO-HciNOpC4b8K3zZnfc,45697
|
| 99 |
+
pydantic/v1/mypy.py,sha256=Cl8XRfCmIcVE3j5AEU52C8iDh8lcX__D3hz2jIWxMAs,38860
|
| 100 |
+
pydantic/v1/networks.py,sha256=HYNtKAfOmOnKJpsDg1g6SIkj9WPhU_-i8l5e2JKBpG4,22124
|
| 101 |
+
pydantic/v1/parse.py,sha256=BJtdqiZRtav9VRFCmOxoY-KImQmjPy-A_NoojiFUZxY,1821
|
| 102 |
+
pydantic/v1/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 103 |
+
pydantic/v1/schema.py,sha256=aqBuA--cq8gAVkim5BJPFASHzOZ8dFtmFX_fNGr6ip4,47801
|
| 104 |
+
pydantic/v1/tools.py,sha256=1lDdXHk0jL5uP3u5RCYAvUAlGClgAO-45lkq9j7fyBA,2881
|
| 105 |
+
pydantic/v1/types.py,sha256=Bzl-RcnitPBHnqwwj9iv7JjHuN1GpnWH24dKkF3l9e8,35455
|
| 106 |
+
pydantic/v1/typing.py,sha256=ovwtLpEZCbnghZaHfSNJupzetzHNkLXjn_66kgTnIV4,20102
|
| 107 |
+
pydantic/v1/utils.py,sha256=1PqOIlz6OVWwGds3HBKlw4Et6asFou0UUpAto7jFOCs,26014
|
| 108 |
+
pydantic/v1/validators.py,sha256=lyUkn1MWhHxlCX5ZfEgFj_CAHojoiPcaQeMdEM9XviU,22187
|
| 109 |
+
pydantic/v1/version.py,sha256=YpHWOQKtGoxfyikzGrcmXJVKUVYB9EBdoCR994QxSnE,1039
|
| 110 |
+
pydantic/validate_call_decorator.py,sha256=VLAi4hoFpjC-1eL0HixYaaWaEFO6htGcDctgsqa5VII,4416
|
| 111 |
+
pydantic/validators.py,sha256=pwbIJXVb1CV2mAE4w_EGfNj7DwzsKaWw_tTL6cviTus,146
|
| 112 |
+
pydantic/version.py,sha256=IZU9hqNGrX_3ZH-zhAb_nWXVDa2MXm5jm7IbAxYxyn4,3985
|
| 113 |
+
pydantic/warnings.py,sha256=3QyQo6lN35cO7OXDbFEXWcNaPlGCRaregVZA-G-lZwI,4822
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/REQUESTED
ADDED
|
File without changes
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: hatchling 1.29.0
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic-2.13.3.dist-info/licenses/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The MIT License (MIT)
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2017 to present Pydantic Services Inc. and individual contributors.
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/__init__.py
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from importlib import import_module
|
| 2 |
+
from typing import TYPE_CHECKING
|
| 3 |
+
from warnings import warn
|
| 4 |
+
|
| 5 |
+
from ._migration import getattr_migration
|
| 6 |
+
from .version import VERSION, _ensure_pydantic_core_version
|
| 7 |
+
|
| 8 |
+
_ensure_pydantic_core_version()
|
| 9 |
+
del _ensure_pydantic_core_version
|
| 10 |
+
|
| 11 |
+
if TYPE_CHECKING:
|
| 12 |
+
# import of virtually everything is supported via `__getattr__` below,
|
| 13 |
+
# but we need them here for type checking and IDE support
|
| 14 |
+
import pydantic_core
|
| 15 |
+
from pydantic_core.core_schema import (
|
| 16 |
+
FieldSerializationInfo,
|
| 17 |
+
SerializationInfo,
|
| 18 |
+
SerializerFunctionWrapHandler,
|
| 19 |
+
ValidationInfo,
|
| 20 |
+
ValidatorFunctionWrapHandler,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
from . import dataclasses
|
| 24 |
+
from .aliases import AliasChoices, AliasGenerator, AliasPath
|
| 25 |
+
from .annotated_handlers import GetCoreSchemaHandler, GetJsonSchemaHandler
|
| 26 |
+
from .config import ConfigDict, with_config
|
| 27 |
+
from .errors import *
|
| 28 |
+
from .fields import Field, PrivateAttr, computed_field
|
| 29 |
+
from .functional_serializers import (
|
| 30 |
+
PlainSerializer,
|
| 31 |
+
SerializeAsAny,
|
| 32 |
+
WrapSerializer,
|
| 33 |
+
field_serializer,
|
| 34 |
+
model_serializer,
|
| 35 |
+
)
|
| 36 |
+
from .functional_validators import (
|
| 37 |
+
AfterValidator,
|
| 38 |
+
BeforeValidator,
|
| 39 |
+
InstanceOf,
|
| 40 |
+
ModelWrapValidatorHandler,
|
| 41 |
+
PlainValidator,
|
| 42 |
+
SkipValidation,
|
| 43 |
+
ValidateAs,
|
| 44 |
+
WrapValidator,
|
| 45 |
+
field_validator,
|
| 46 |
+
model_validator,
|
| 47 |
+
)
|
| 48 |
+
from .json_schema import WithJsonSchema
|
| 49 |
+
from .main import *
|
| 50 |
+
from .networks import *
|
| 51 |
+
from .type_adapter import TypeAdapter
|
| 52 |
+
from .types import *
|
| 53 |
+
from .validate_call_decorator import validate_call
|
| 54 |
+
from .warnings import (
|
| 55 |
+
PydanticDeprecatedSince20,
|
| 56 |
+
PydanticDeprecatedSince26,
|
| 57 |
+
PydanticDeprecatedSince29,
|
| 58 |
+
PydanticDeprecatedSince210,
|
| 59 |
+
PydanticDeprecatedSince211,
|
| 60 |
+
PydanticDeprecatedSince212,
|
| 61 |
+
PydanticDeprecationWarning,
|
| 62 |
+
PydanticExperimentalWarning,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
# this encourages pycharm to import `ValidationError` from here, not pydantic_core
|
| 66 |
+
ValidationError = pydantic_core.ValidationError
|
| 67 |
+
from .deprecated.class_validators import root_validator, validator
|
| 68 |
+
from .deprecated.config import BaseConfig, Extra
|
| 69 |
+
from .deprecated.tools import *
|
| 70 |
+
from .root_model import RootModel
|
| 71 |
+
|
| 72 |
+
__version__ = VERSION
|
| 73 |
+
__all__ = (
|
| 74 |
+
# dataclasses
|
| 75 |
+
'dataclasses',
|
| 76 |
+
# functional validators
|
| 77 |
+
'field_validator',
|
| 78 |
+
'model_validator',
|
| 79 |
+
'AfterValidator',
|
| 80 |
+
'BeforeValidator',
|
| 81 |
+
'PlainValidator',
|
| 82 |
+
'WrapValidator',
|
| 83 |
+
'SkipValidation',
|
| 84 |
+
'ValidateAs',
|
| 85 |
+
'InstanceOf',
|
| 86 |
+
'ModelWrapValidatorHandler',
|
| 87 |
+
# JSON Schema
|
| 88 |
+
'WithJsonSchema',
|
| 89 |
+
# deprecated V1 functional validators, these are imported via `__getattr__` below
|
| 90 |
+
'root_validator',
|
| 91 |
+
'validator',
|
| 92 |
+
# functional serializers
|
| 93 |
+
'field_serializer',
|
| 94 |
+
'model_serializer',
|
| 95 |
+
'PlainSerializer',
|
| 96 |
+
'SerializeAsAny',
|
| 97 |
+
'WrapSerializer',
|
| 98 |
+
# config
|
| 99 |
+
'ConfigDict',
|
| 100 |
+
'with_config',
|
| 101 |
+
# deprecated V1 config, these are imported via `__getattr__` below
|
| 102 |
+
'BaseConfig',
|
| 103 |
+
'Extra',
|
| 104 |
+
# validate_call
|
| 105 |
+
'validate_call',
|
| 106 |
+
# errors
|
| 107 |
+
'PydanticErrorCodes',
|
| 108 |
+
'PydanticUserError',
|
| 109 |
+
'PydanticSchemaGenerationError',
|
| 110 |
+
'PydanticImportError',
|
| 111 |
+
'PydanticUndefinedAnnotation',
|
| 112 |
+
'PydanticInvalidForJsonSchema',
|
| 113 |
+
'PydanticForbiddenQualifier',
|
| 114 |
+
# fields
|
| 115 |
+
'Field',
|
| 116 |
+
'computed_field',
|
| 117 |
+
'PrivateAttr',
|
| 118 |
+
# alias
|
| 119 |
+
'AliasChoices',
|
| 120 |
+
'AliasGenerator',
|
| 121 |
+
'AliasPath',
|
| 122 |
+
# main
|
| 123 |
+
'BaseModel',
|
| 124 |
+
'create_model',
|
| 125 |
+
# network
|
| 126 |
+
'AnyUrl',
|
| 127 |
+
'AnyHttpUrl',
|
| 128 |
+
'FileUrl',
|
| 129 |
+
'HttpUrl',
|
| 130 |
+
'FtpUrl',
|
| 131 |
+
'WebsocketUrl',
|
| 132 |
+
'AnyWebsocketUrl',
|
| 133 |
+
'UrlConstraints',
|
| 134 |
+
'EmailStr',
|
| 135 |
+
'NameEmail',
|
| 136 |
+
'IPvAnyAddress',
|
| 137 |
+
'IPvAnyInterface',
|
| 138 |
+
'IPvAnyNetwork',
|
| 139 |
+
'PostgresDsn',
|
| 140 |
+
'CockroachDsn',
|
| 141 |
+
'AmqpDsn',
|
| 142 |
+
'RedisDsn',
|
| 143 |
+
'MongoDsn',
|
| 144 |
+
'KafkaDsn',
|
| 145 |
+
'NatsDsn',
|
| 146 |
+
'MySQLDsn',
|
| 147 |
+
'MariaDBDsn',
|
| 148 |
+
'ClickHouseDsn',
|
| 149 |
+
'SnowflakeDsn',
|
| 150 |
+
'validate_email',
|
| 151 |
+
# root_model
|
| 152 |
+
'RootModel',
|
| 153 |
+
# deprecated tools, these are imported via `__getattr__` below
|
| 154 |
+
'parse_obj_as',
|
| 155 |
+
'schema_of',
|
| 156 |
+
'schema_json_of',
|
| 157 |
+
# types
|
| 158 |
+
'Strict',
|
| 159 |
+
'StrictStr',
|
| 160 |
+
'conbytes',
|
| 161 |
+
'conlist',
|
| 162 |
+
'conset',
|
| 163 |
+
'confrozenset',
|
| 164 |
+
'constr',
|
| 165 |
+
'StringConstraints',
|
| 166 |
+
'ImportString',
|
| 167 |
+
'conint',
|
| 168 |
+
'PositiveInt',
|
| 169 |
+
'NegativeInt',
|
| 170 |
+
'NonNegativeInt',
|
| 171 |
+
'NonPositiveInt',
|
| 172 |
+
'confloat',
|
| 173 |
+
'PositiveFloat',
|
| 174 |
+
'NegativeFloat',
|
| 175 |
+
'NonNegativeFloat',
|
| 176 |
+
'NonPositiveFloat',
|
| 177 |
+
'FiniteFloat',
|
| 178 |
+
'condecimal',
|
| 179 |
+
'condate',
|
| 180 |
+
'UUID1',
|
| 181 |
+
'UUID3',
|
| 182 |
+
'UUID4',
|
| 183 |
+
'UUID5',
|
| 184 |
+
'UUID6',
|
| 185 |
+
'UUID7',
|
| 186 |
+
'UUID8',
|
| 187 |
+
'FilePath',
|
| 188 |
+
'DirectoryPath',
|
| 189 |
+
'NewPath',
|
| 190 |
+
'Json',
|
| 191 |
+
'Secret',
|
| 192 |
+
'SecretStr',
|
| 193 |
+
'SecretBytes',
|
| 194 |
+
'SocketPath',
|
| 195 |
+
'StrictBool',
|
| 196 |
+
'StrictBytes',
|
| 197 |
+
'StrictInt',
|
| 198 |
+
'StrictFloat',
|
| 199 |
+
'PaymentCardNumber',
|
| 200 |
+
'ByteSize',
|
| 201 |
+
'PastDate',
|
| 202 |
+
'FutureDate',
|
| 203 |
+
'PastDatetime',
|
| 204 |
+
'FutureDatetime',
|
| 205 |
+
'AwareDatetime',
|
| 206 |
+
'NaiveDatetime',
|
| 207 |
+
'AllowInfNan',
|
| 208 |
+
'EncoderProtocol',
|
| 209 |
+
'EncodedBytes',
|
| 210 |
+
'EncodedStr',
|
| 211 |
+
'Base64Encoder',
|
| 212 |
+
'Base64Bytes',
|
| 213 |
+
'Base64Str',
|
| 214 |
+
'Base64UrlBytes',
|
| 215 |
+
'Base64UrlStr',
|
| 216 |
+
'GetPydanticSchema',
|
| 217 |
+
'Tag',
|
| 218 |
+
'Discriminator',
|
| 219 |
+
'JsonValue',
|
| 220 |
+
'FailFast',
|
| 221 |
+
# type_adapter
|
| 222 |
+
'TypeAdapter',
|
| 223 |
+
# version
|
| 224 |
+
'__version__',
|
| 225 |
+
'VERSION',
|
| 226 |
+
# warnings
|
| 227 |
+
'PydanticDeprecatedSince20',
|
| 228 |
+
'PydanticDeprecatedSince26',
|
| 229 |
+
'PydanticDeprecatedSince29',
|
| 230 |
+
'PydanticDeprecatedSince210',
|
| 231 |
+
'PydanticDeprecatedSince211',
|
| 232 |
+
'PydanticDeprecatedSince212',
|
| 233 |
+
'PydanticDeprecationWarning',
|
| 234 |
+
'PydanticExperimentalWarning',
|
| 235 |
+
# annotated handlers
|
| 236 |
+
'GetCoreSchemaHandler',
|
| 237 |
+
'GetJsonSchemaHandler',
|
| 238 |
+
# pydantic_core
|
| 239 |
+
'ValidationError',
|
| 240 |
+
'ValidationInfo',
|
| 241 |
+
'SerializationInfo',
|
| 242 |
+
'ValidatorFunctionWrapHandler',
|
| 243 |
+
'FieldSerializationInfo',
|
| 244 |
+
'SerializerFunctionWrapHandler',
|
| 245 |
+
'OnErrorOmit',
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
# A mapping of {<member name>: (package, <module name>)} defining dynamic imports
|
| 249 |
+
_dynamic_imports: 'dict[str, tuple[str, str]]' = {
|
| 250 |
+
'dataclasses': (__spec__.parent, '__module__'),
|
| 251 |
+
# functional validators
|
| 252 |
+
'field_validator': (__spec__.parent, '.functional_validators'),
|
| 253 |
+
'model_validator': (__spec__.parent, '.functional_validators'),
|
| 254 |
+
'AfterValidator': (__spec__.parent, '.functional_validators'),
|
| 255 |
+
'BeforeValidator': (__spec__.parent, '.functional_validators'),
|
| 256 |
+
'PlainValidator': (__spec__.parent, '.functional_validators'),
|
| 257 |
+
'WrapValidator': (__spec__.parent, '.functional_validators'),
|
| 258 |
+
'SkipValidation': (__spec__.parent, '.functional_validators'),
|
| 259 |
+
'InstanceOf': (__spec__.parent, '.functional_validators'),
|
| 260 |
+
'ValidateAs': (__spec__.parent, '.functional_validators'),
|
| 261 |
+
'ModelWrapValidatorHandler': (__spec__.parent, '.functional_validators'),
|
| 262 |
+
# JSON Schema
|
| 263 |
+
'WithJsonSchema': (__spec__.parent, '.json_schema'),
|
| 264 |
+
# functional serializers
|
| 265 |
+
'field_serializer': (__spec__.parent, '.functional_serializers'),
|
| 266 |
+
'model_serializer': (__spec__.parent, '.functional_serializers'),
|
| 267 |
+
'PlainSerializer': (__spec__.parent, '.functional_serializers'),
|
| 268 |
+
'SerializeAsAny': (__spec__.parent, '.functional_serializers'),
|
| 269 |
+
'WrapSerializer': (__spec__.parent, '.functional_serializers'),
|
| 270 |
+
# config
|
| 271 |
+
'ConfigDict': (__spec__.parent, '.config'),
|
| 272 |
+
'with_config': (__spec__.parent, '.config'),
|
| 273 |
+
# validate call
|
| 274 |
+
'validate_call': (__spec__.parent, '.validate_call_decorator'),
|
| 275 |
+
# errors
|
| 276 |
+
'PydanticErrorCodes': (__spec__.parent, '.errors'),
|
| 277 |
+
'PydanticUserError': (__spec__.parent, '.errors'),
|
| 278 |
+
'PydanticSchemaGenerationError': (__spec__.parent, '.errors'),
|
| 279 |
+
'PydanticImportError': (__spec__.parent, '.errors'),
|
| 280 |
+
'PydanticUndefinedAnnotation': (__spec__.parent, '.errors'),
|
| 281 |
+
'PydanticInvalidForJsonSchema': (__spec__.parent, '.errors'),
|
| 282 |
+
'PydanticForbiddenQualifier': (__spec__.parent, '.errors'),
|
| 283 |
+
# fields
|
| 284 |
+
'Field': (__spec__.parent, '.fields'),
|
| 285 |
+
'computed_field': (__spec__.parent, '.fields'),
|
| 286 |
+
'PrivateAttr': (__spec__.parent, '.fields'),
|
| 287 |
+
# alias
|
| 288 |
+
'AliasChoices': (__spec__.parent, '.aliases'),
|
| 289 |
+
'AliasGenerator': (__spec__.parent, '.aliases'),
|
| 290 |
+
'AliasPath': (__spec__.parent, '.aliases'),
|
| 291 |
+
# main
|
| 292 |
+
'BaseModel': (__spec__.parent, '.main'),
|
| 293 |
+
'create_model': (__spec__.parent, '.main'),
|
| 294 |
+
# network
|
| 295 |
+
'AnyUrl': (__spec__.parent, '.networks'),
|
| 296 |
+
'AnyHttpUrl': (__spec__.parent, '.networks'),
|
| 297 |
+
'FileUrl': (__spec__.parent, '.networks'),
|
| 298 |
+
'HttpUrl': (__spec__.parent, '.networks'),
|
| 299 |
+
'FtpUrl': (__spec__.parent, '.networks'),
|
| 300 |
+
'WebsocketUrl': (__spec__.parent, '.networks'),
|
| 301 |
+
'AnyWebsocketUrl': (__spec__.parent, '.networks'),
|
| 302 |
+
'UrlConstraints': (__spec__.parent, '.networks'),
|
| 303 |
+
'EmailStr': (__spec__.parent, '.networks'),
|
| 304 |
+
'NameEmail': (__spec__.parent, '.networks'),
|
| 305 |
+
'IPvAnyAddress': (__spec__.parent, '.networks'),
|
| 306 |
+
'IPvAnyInterface': (__spec__.parent, '.networks'),
|
| 307 |
+
'IPvAnyNetwork': (__spec__.parent, '.networks'),
|
| 308 |
+
'PostgresDsn': (__spec__.parent, '.networks'),
|
| 309 |
+
'CockroachDsn': (__spec__.parent, '.networks'),
|
| 310 |
+
'AmqpDsn': (__spec__.parent, '.networks'),
|
| 311 |
+
'RedisDsn': (__spec__.parent, '.networks'),
|
| 312 |
+
'MongoDsn': (__spec__.parent, '.networks'),
|
| 313 |
+
'KafkaDsn': (__spec__.parent, '.networks'),
|
| 314 |
+
'NatsDsn': (__spec__.parent, '.networks'),
|
| 315 |
+
'MySQLDsn': (__spec__.parent, '.networks'),
|
| 316 |
+
'MariaDBDsn': (__spec__.parent, '.networks'),
|
| 317 |
+
'ClickHouseDsn': (__spec__.parent, '.networks'),
|
| 318 |
+
'SnowflakeDsn': (__spec__.parent, '.networks'),
|
| 319 |
+
'validate_email': (__spec__.parent, '.networks'),
|
| 320 |
+
# root_model
|
| 321 |
+
'RootModel': (__spec__.parent, '.root_model'),
|
| 322 |
+
# types
|
| 323 |
+
'Strict': (__spec__.parent, '.types'),
|
| 324 |
+
'StrictStr': (__spec__.parent, '.types'),
|
| 325 |
+
'conbytes': (__spec__.parent, '.types'),
|
| 326 |
+
'conlist': (__spec__.parent, '.types'),
|
| 327 |
+
'conset': (__spec__.parent, '.types'),
|
| 328 |
+
'confrozenset': (__spec__.parent, '.types'),
|
| 329 |
+
'constr': (__spec__.parent, '.types'),
|
| 330 |
+
'StringConstraints': (__spec__.parent, '.types'),
|
| 331 |
+
'ImportString': (__spec__.parent, '.types'),
|
| 332 |
+
'conint': (__spec__.parent, '.types'),
|
| 333 |
+
'PositiveInt': (__spec__.parent, '.types'),
|
| 334 |
+
'NegativeInt': (__spec__.parent, '.types'),
|
| 335 |
+
'NonNegativeInt': (__spec__.parent, '.types'),
|
| 336 |
+
'NonPositiveInt': (__spec__.parent, '.types'),
|
| 337 |
+
'confloat': (__spec__.parent, '.types'),
|
| 338 |
+
'PositiveFloat': (__spec__.parent, '.types'),
|
| 339 |
+
'NegativeFloat': (__spec__.parent, '.types'),
|
| 340 |
+
'NonNegativeFloat': (__spec__.parent, '.types'),
|
| 341 |
+
'NonPositiveFloat': (__spec__.parent, '.types'),
|
| 342 |
+
'FiniteFloat': (__spec__.parent, '.types'),
|
| 343 |
+
'condecimal': (__spec__.parent, '.types'),
|
| 344 |
+
'condate': (__spec__.parent, '.types'),
|
| 345 |
+
'UUID1': (__spec__.parent, '.types'),
|
| 346 |
+
'UUID3': (__spec__.parent, '.types'),
|
| 347 |
+
'UUID4': (__spec__.parent, '.types'),
|
| 348 |
+
'UUID5': (__spec__.parent, '.types'),
|
| 349 |
+
'UUID6': (__spec__.parent, '.types'),
|
| 350 |
+
'UUID7': (__spec__.parent, '.types'),
|
| 351 |
+
'UUID8': (__spec__.parent, '.types'),
|
| 352 |
+
'FilePath': (__spec__.parent, '.types'),
|
| 353 |
+
'DirectoryPath': (__spec__.parent, '.types'),
|
| 354 |
+
'NewPath': (__spec__.parent, '.types'),
|
| 355 |
+
'Json': (__spec__.parent, '.types'),
|
| 356 |
+
'Secret': (__spec__.parent, '.types'),
|
| 357 |
+
'SecretStr': (__spec__.parent, '.types'),
|
| 358 |
+
'SecretBytes': (__spec__.parent, '.types'),
|
| 359 |
+
'StrictBool': (__spec__.parent, '.types'),
|
| 360 |
+
'StrictBytes': (__spec__.parent, '.types'),
|
| 361 |
+
'StrictInt': (__spec__.parent, '.types'),
|
| 362 |
+
'StrictFloat': (__spec__.parent, '.types'),
|
| 363 |
+
'PaymentCardNumber': (__spec__.parent, '.types'),
|
| 364 |
+
'ByteSize': (__spec__.parent, '.types'),
|
| 365 |
+
'PastDate': (__spec__.parent, '.types'),
|
| 366 |
+
'SocketPath': (__spec__.parent, '.types'),
|
| 367 |
+
'FutureDate': (__spec__.parent, '.types'),
|
| 368 |
+
'PastDatetime': (__spec__.parent, '.types'),
|
| 369 |
+
'FutureDatetime': (__spec__.parent, '.types'),
|
| 370 |
+
'AwareDatetime': (__spec__.parent, '.types'),
|
| 371 |
+
'NaiveDatetime': (__spec__.parent, '.types'),
|
| 372 |
+
'AllowInfNan': (__spec__.parent, '.types'),
|
| 373 |
+
'EncoderProtocol': (__spec__.parent, '.types'),
|
| 374 |
+
'EncodedBytes': (__spec__.parent, '.types'),
|
| 375 |
+
'EncodedStr': (__spec__.parent, '.types'),
|
| 376 |
+
'Base64Encoder': (__spec__.parent, '.types'),
|
| 377 |
+
'Base64Bytes': (__spec__.parent, '.types'),
|
| 378 |
+
'Base64Str': (__spec__.parent, '.types'),
|
| 379 |
+
'Base64UrlBytes': (__spec__.parent, '.types'),
|
| 380 |
+
'Base64UrlStr': (__spec__.parent, '.types'),
|
| 381 |
+
'GetPydanticSchema': (__spec__.parent, '.types'),
|
| 382 |
+
'Tag': (__spec__.parent, '.types'),
|
| 383 |
+
'Discriminator': (__spec__.parent, '.types'),
|
| 384 |
+
'JsonValue': (__spec__.parent, '.types'),
|
| 385 |
+
'OnErrorOmit': (__spec__.parent, '.types'),
|
| 386 |
+
'FailFast': (__spec__.parent, '.types'),
|
| 387 |
+
# type_adapter
|
| 388 |
+
'TypeAdapter': (__spec__.parent, '.type_adapter'),
|
| 389 |
+
# warnings
|
| 390 |
+
'PydanticDeprecatedSince20': (__spec__.parent, '.warnings'),
|
| 391 |
+
'PydanticDeprecatedSince26': (__spec__.parent, '.warnings'),
|
| 392 |
+
'PydanticDeprecatedSince29': (__spec__.parent, '.warnings'),
|
| 393 |
+
'PydanticDeprecatedSince210': (__spec__.parent, '.warnings'),
|
| 394 |
+
'PydanticDeprecatedSince211': (__spec__.parent, '.warnings'),
|
| 395 |
+
'PydanticDeprecatedSince212': (__spec__.parent, '.warnings'),
|
| 396 |
+
'PydanticDeprecationWarning': (__spec__.parent, '.warnings'),
|
| 397 |
+
'PydanticExperimentalWarning': (__spec__.parent, '.warnings'),
|
| 398 |
+
# annotated handlers
|
| 399 |
+
'GetCoreSchemaHandler': (__spec__.parent, '.annotated_handlers'),
|
| 400 |
+
'GetJsonSchemaHandler': (__spec__.parent, '.annotated_handlers'),
|
| 401 |
+
# pydantic_core stuff
|
| 402 |
+
'ValidationError': ('pydantic_core', '.'),
|
| 403 |
+
'ValidationInfo': ('pydantic_core', '.core_schema'),
|
| 404 |
+
'SerializationInfo': ('pydantic_core', '.core_schema'),
|
| 405 |
+
'ValidatorFunctionWrapHandler': ('pydantic_core', '.core_schema'),
|
| 406 |
+
'FieldSerializationInfo': ('pydantic_core', '.core_schema'),
|
| 407 |
+
'SerializerFunctionWrapHandler': ('pydantic_core', '.core_schema'),
|
| 408 |
+
# deprecated, mostly not included in __all__
|
| 409 |
+
'root_validator': (__spec__.parent, '.deprecated.class_validators'),
|
| 410 |
+
'validator': (__spec__.parent, '.deprecated.class_validators'),
|
| 411 |
+
'BaseConfig': (__spec__.parent, '.deprecated.config'),
|
| 412 |
+
'Extra': (__spec__.parent, '.deprecated.config'),
|
| 413 |
+
'parse_obj_as': (__spec__.parent, '.deprecated.tools'),
|
| 414 |
+
'schema_of': (__spec__.parent, '.deprecated.tools'),
|
| 415 |
+
'schema_json_of': (__spec__.parent, '.deprecated.tools'),
|
| 416 |
+
# deprecated dynamic imports
|
| 417 |
+
'FieldValidationInfo': ('pydantic_core', '.core_schema'),
|
| 418 |
+
'GenerateSchema': (__spec__.parent, '._internal._generate_schema'),
|
| 419 |
+
}
|
| 420 |
+
_deprecated_dynamic_imports = {'FieldValidationInfo', 'GenerateSchema'}
|
| 421 |
+
|
| 422 |
+
_getattr_migration = getattr_migration(__name__)
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
def __getattr__(attr_name: str) -> object:
|
| 426 |
+
if attr_name in _deprecated_dynamic_imports:
|
| 427 |
+
from pydantic.warnings import PydanticDeprecatedSince20
|
| 428 |
+
|
| 429 |
+
warn(
|
| 430 |
+
f'Importing {attr_name} from `pydantic` is deprecated. This feature is either no longer supported, or is not public.',
|
| 431 |
+
PydanticDeprecatedSince20,
|
| 432 |
+
stacklevel=2,
|
| 433 |
+
)
|
| 434 |
+
|
| 435 |
+
dynamic_attr = _dynamic_imports.get(attr_name)
|
| 436 |
+
if dynamic_attr is None:
|
| 437 |
+
return _getattr_migration(attr_name)
|
| 438 |
+
|
| 439 |
+
package, module_name = dynamic_attr
|
| 440 |
+
|
| 441 |
+
if module_name == '__module__':
|
| 442 |
+
result = import_module(f'.{attr_name}', package=package)
|
| 443 |
+
globals()[attr_name] = result
|
| 444 |
+
return result
|
| 445 |
+
else:
|
| 446 |
+
module = import_module(module_name, package=package)
|
| 447 |
+
result = getattr(module, attr_name)
|
| 448 |
+
g = globals()
|
| 449 |
+
for k, (_, v_module_name) in _dynamic_imports.items():
|
| 450 |
+
if v_module_name == module_name and k not in _deprecated_dynamic_imports:
|
| 451 |
+
g[k] = getattr(module, k)
|
| 452 |
+
return result
|
| 453 |
+
|
| 454 |
+
|
| 455 |
+
def __dir__() -> list[str]:
|
| 456 |
+
return list(__all__)
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/__init__.py
ADDED
|
File without changes
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_config.py
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations as _annotations
|
| 2 |
+
|
| 3 |
+
import warnings
|
| 4 |
+
from contextlib import contextmanager
|
| 5 |
+
from re import Pattern
|
| 6 |
+
from typing import (
|
| 7 |
+
TYPE_CHECKING,
|
| 8 |
+
Any,
|
| 9 |
+
Callable,
|
| 10 |
+
Literal,
|
| 11 |
+
cast,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
from pydantic_core import core_schema
|
| 15 |
+
from typing_extensions import Self
|
| 16 |
+
|
| 17 |
+
from ..aliases import AliasGenerator
|
| 18 |
+
from ..config import ConfigDict, ExtraValues, JsonDict, JsonEncoder, JsonSchemaExtraCallable
|
| 19 |
+
from ..errors import PydanticUserError
|
| 20 |
+
from ..warnings import PydanticDeprecatedSince20, PydanticDeprecatedSince210
|
| 21 |
+
|
| 22 |
+
if TYPE_CHECKING:
|
| 23 |
+
from .._internal._schema_generation_shared import GenerateSchema
|
| 24 |
+
from ..fields import ComputedFieldInfo, FieldInfo
|
| 25 |
+
|
| 26 |
+
DEPRECATION_MESSAGE = 'Support for class-based `config` is deprecated, use ConfigDict instead.'
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class ConfigWrapper:
|
| 30 |
+
"""Internal wrapper for Config which exposes ConfigDict items as attributes."""
|
| 31 |
+
|
| 32 |
+
__slots__ = ('config_dict',)
|
| 33 |
+
|
| 34 |
+
config_dict: ConfigDict
|
| 35 |
+
|
| 36 |
+
# all annotations are copied directly from ConfigDict, and should be kept up to date, a test will fail if they
|
| 37 |
+
# stop matching
|
| 38 |
+
title: str | None
|
| 39 |
+
str_to_lower: bool
|
| 40 |
+
str_to_upper: bool
|
| 41 |
+
str_strip_whitespace: bool
|
| 42 |
+
str_min_length: int
|
| 43 |
+
str_max_length: int | None
|
| 44 |
+
extra: ExtraValues | None
|
| 45 |
+
frozen: bool
|
| 46 |
+
populate_by_name: bool
|
| 47 |
+
use_enum_values: bool
|
| 48 |
+
validate_assignment: bool
|
| 49 |
+
arbitrary_types_allowed: bool
|
| 50 |
+
from_attributes: bool
|
| 51 |
+
# whether to use the actual key provided in the data (e.g. alias or first alias for "field required" errors) instead of field_names
|
| 52 |
+
# to construct error `loc`s, default `True`
|
| 53 |
+
loc_by_alias: bool
|
| 54 |
+
alias_generator: Callable[[str], str] | AliasGenerator | None
|
| 55 |
+
model_title_generator: Callable[[type], str] | None
|
| 56 |
+
field_title_generator: Callable[[str, FieldInfo | ComputedFieldInfo], str] | None
|
| 57 |
+
ignored_types: tuple[type, ...]
|
| 58 |
+
allow_inf_nan: bool
|
| 59 |
+
json_schema_extra: JsonDict | JsonSchemaExtraCallable | None
|
| 60 |
+
json_encoders: dict[type[object], JsonEncoder] | None
|
| 61 |
+
|
| 62 |
+
# new in V2
|
| 63 |
+
strict: bool
|
| 64 |
+
# whether instances of models and dataclasses (including subclass instances) should re-validate, default 'never'
|
| 65 |
+
revalidate_instances: Literal['always', 'never', 'subclass-instances']
|
| 66 |
+
ser_json_timedelta: Literal['iso8601', 'float']
|
| 67 |
+
ser_json_temporal: Literal['iso8601', 'seconds', 'milliseconds']
|
| 68 |
+
val_temporal_unit: Literal['seconds', 'milliseconds', 'infer']
|
| 69 |
+
ser_json_bytes: Literal['utf8', 'base64', 'hex']
|
| 70 |
+
val_json_bytes: Literal['utf8', 'base64', 'hex']
|
| 71 |
+
ser_json_inf_nan: Literal['null', 'constants', 'strings']
|
| 72 |
+
# whether to validate default values during validation, default False
|
| 73 |
+
validate_default: bool
|
| 74 |
+
validate_return: bool
|
| 75 |
+
protected_namespaces: tuple[str | Pattern[str], ...]
|
| 76 |
+
hide_input_in_errors: bool
|
| 77 |
+
defer_build: bool
|
| 78 |
+
plugin_settings: dict[str, object] | None
|
| 79 |
+
schema_generator: type[GenerateSchema] | None
|
| 80 |
+
json_schema_serialization_defaults_required: bool
|
| 81 |
+
json_schema_mode_override: Literal['validation', 'serialization', None]
|
| 82 |
+
coerce_numbers_to_str: bool
|
| 83 |
+
regex_engine: Literal['rust-regex', 'python-re']
|
| 84 |
+
validation_error_cause: bool
|
| 85 |
+
use_attribute_docstrings: bool
|
| 86 |
+
cache_strings: bool | Literal['all', 'keys', 'none']
|
| 87 |
+
validate_by_alias: bool
|
| 88 |
+
validate_by_name: bool
|
| 89 |
+
serialize_by_alias: bool
|
| 90 |
+
url_preserve_empty_path: bool
|
| 91 |
+
polymorphic_serialization: bool
|
| 92 |
+
|
| 93 |
+
def __init__(self, config: ConfigDict | dict[str, Any] | type[Any] | None, *, check: bool = True):
|
| 94 |
+
if check:
|
| 95 |
+
self.config_dict = prepare_config(config)
|
| 96 |
+
else:
|
| 97 |
+
self.config_dict = cast(ConfigDict, config)
|
| 98 |
+
|
| 99 |
+
@classmethod
|
| 100 |
+
def for_model(
|
| 101 |
+
cls,
|
| 102 |
+
bases: tuple[type[Any], ...],
|
| 103 |
+
namespace: dict[str, Any],
|
| 104 |
+
raw_annotations: dict[str, Any],
|
| 105 |
+
kwargs: dict[str, Any],
|
| 106 |
+
) -> Self:
|
| 107 |
+
"""Build a new `ConfigWrapper` instance for a `BaseModel`.
|
| 108 |
+
|
| 109 |
+
The config wrapper built based on (in descending order of priority):
|
| 110 |
+
- options from `kwargs`
|
| 111 |
+
- options from the `namespace`
|
| 112 |
+
- options from the base classes (`bases`)
|
| 113 |
+
|
| 114 |
+
Args:
|
| 115 |
+
bases: A tuple of base classes.
|
| 116 |
+
namespace: The namespace of the class being created.
|
| 117 |
+
raw_annotations: The (non-evaluated) annotations of the model.
|
| 118 |
+
kwargs: The kwargs passed to the class being created.
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
A `ConfigWrapper` instance for `BaseModel`.
|
| 122 |
+
"""
|
| 123 |
+
config_new = ConfigDict()
|
| 124 |
+
for base in bases:
|
| 125 |
+
config = getattr(base, 'model_config', None)
|
| 126 |
+
if config:
|
| 127 |
+
config_new.update(config.copy())
|
| 128 |
+
|
| 129 |
+
config_class_from_namespace = namespace.get('Config')
|
| 130 |
+
config_dict_from_namespace = namespace.get('model_config')
|
| 131 |
+
|
| 132 |
+
if raw_annotations.get('model_config') and config_dict_from_namespace is None:
|
| 133 |
+
raise PydanticUserError(
|
| 134 |
+
'`model_config` cannot be used as a model field name. Use `model_config` for model configuration.',
|
| 135 |
+
code='model-config-invalid-field-name',
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
if config_class_from_namespace and config_dict_from_namespace:
|
| 139 |
+
raise PydanticUserError('"Config" and "model_config" cannot be used together', code='config-both')
|
| 140 |
+
|
| 141 |
+
config_from_namespace = config_dict_from_namespace or prepare_config(config_class_from_namespace)
|
| 142 |
+
|
| 143 |
+
config_new.update(config_from_namespace)
|
| 144 |
+
|
| 145 |
+
for k in list(kwargs.keys()):
|
| 146 |
+
if k in config_keys:
|
| 147 |
+
config_new[k] = kwargs.pop(k)
|
| 148 |
+
|
| 149 |
+
return cls(config_new)
|
| 150 |
+
|
| 151 |
+
# we don't show `__getattr__` to type checkers so missing attributes cause errors
|
| 152 |
+
if not TYPE_CHECKING: # pragma: no branch
|
| 153 |
+
|
| 154 |
+
def __getattr__(self, name: str) -> Any:
|
| 155 |
+
try:
|
| 156 |
+
return self.config_dict[name]
|
| 157 |
+
except KeyError:
|
| 158 |
+
try:
|
| 159 |
+
return config_defaults[name]
|
| 160 |
+
except KeyError:
|
| 161 |
+
raise AttributeError(f'Config has no attribute {name!r}') from None
|
| 162 |
+
|
| 163 |
+
def core_config(self, title: str | None) -> core_schema.CoreConfig:
|
| 164 |
+
"""Create a pydantic-core config.
|
| 165 |
+
|
| 166 |
+
We don't use getattr here since we don't want to populate with defaults.
|
| 167 |
+
|
| 168 |
+
Args:
|
| 169 |
+
title: The title to use if not set in config.
|
| 170 |
+
|
| 171 |
+
Returns:
|
| 172 |
+
A `CoreConfig` object created from config.
|
| 173 |
+
"""
|
| 174 |
+
config = self.config_dict
|
| 175 |
+
|
| 176 |
+
if config.get('schema_generator') is not None:
|
| 177 |
+
warnings.warn(
|
| 178 |
+
'The `schema_generator` setting has been deprecated since v2.10. This setting no longer has any effect.',
|
| 179 |
+
PydanticDeprecatedSince210,
|
| 180 |
+
stacklevel=2,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
if (populate_by_name := config.get('populate_by_name')) is not None:
|
| 184 |
+
# We include this patch for backwards compatibility purposes, but this config setting will be deprecated in v3.0, and likely removed in v4.0.
|
| 185 |
+
# Thus, the above warning and this patch can be removed then as well.
|
| 186 |
+
if config.get('validate_by_name') is None:
|
| 187 |
+
config['validate_by_alias'] = True
|
| 188 |
+
config['validate_by_name'] = populate_by_name
|
| 189 |
+
|
| 190 |
+
# We dynamically patch validate_by_name to be True if validate_by_alias is set to False
|
| 191 |
+
# and validate_by_name is not explicitly set.
|
| 192 |
+
if config.get('validate_by_alias') is False and config.get('validate_by_name') is None:
|
| 193 |
+
config['validate_by_name'] = True
|
| 194 |
+
|
| 195 |
+
if (not config.get('validate_by_alias', True)) and (not config.get('validate_by_name', False)):
|
| 196 |
+
raise PydanticUserError(
|
| 197 |
+
'At least one of `validate_by_alias` or `validate_by_name` must be set to True.',
|
| 198 |
+
code='validate-by-alias-and-name-false',
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
return core_schema.CoreConfig(
|
| 202 |
+
**{ # pyright: ignore[reportArgumentType]
|
| 203 |
+
k: v
|
| 204 |
+
for k, v in (
|
| 205 |
+
('title', config.get('title') or title or None),
|
| 206 |
+
('extra_fields_behavior', config.get('extra')),
|
| 207 |
+
('allow_inf_nan', config.get('allow_inf_nan')),
|
| 208 |
+
('str_strip_whitespace', config.get('str_strip_whitespace')),
|
| 209 |
+
('str_to_lower', config.get('str_to_lower')),
|
| 210 |
+
('str_to_upper', config.get('str_to_upper')),
|
| 211 |
+
('strict', config.get('strict')),
|
| 212 |
+
('ser_json_timedelta', config.get('ser_json_timedelta')),
|
| 213 |
+
('ser_json_temporal', config.get('ser_json_temporal')),
|
| 214 |
+
('val_temporal_unit', config.get('val_temporal_unit')),
|
| 215 |
+
('ser_json_bytes', config.get('ser_json_bytes')),
|
| 216 |
+
('val_json_bytes', config.get('val_json_bytes')),
|
| 217 |
+
('ser_json_inf_nan', config.get('ser_json_inf_nan')),
|
| 218 |
+
('from_attributes', config.get('from_attributes')),
|
| 219 |
+
('loc_by_alias', config.get('loc_by_alias')),
|
| 220 |
+
('revalidate_instances', config.get('revalidate_instances')),
|
| 221 |
+
('validate_default', config.get('validate_default')),
|
| 222 |
+
('str_max_length', config.get('str_max_length')),
|
| 223 |
+
('str_min_length', config.get('str_min_length')),
|
| 224 |
+
('hide_input_in_errors', config.get('hide_input_in_errors')),
|
| 225 |
+
('coerce_numbers_to_str', config.get('coerce_numbers_to_str')),
|
| 226 |
+
('regex_engine', config.get('regex_engine')),
|
| 227 |
+
('validation_error_cause', config.get('validation_error_cause')),
|
| 228 |
+
('cache_strings', config.get('cache_strings')),
|
| 229 |
+
('validate_by_alias', config.get('validate_by_alias')),
|
| 230 |
+
('validate_by_name', config.get('validate_by_name')),
|
| 231 |
+
('serialize_by_alias', config.get('serialize_by_alias')),
|
| 232 |
+
('url_preserve_empty_path', config.get('url_preserve_empty_path')),
|
| 233 |
+
('polymorphic_serialization', config.get('polymorphic_serialization')),
|
| 234 |
+
)
|
| 235 |
+
if v is not None
|
| 236 |
+
}
|
| 237 |
+
)
|
| 238 |
+
|
| 239 |
+
def __repr__(self):
|
| 240 |
+
c = ', '.join(f'{k}={v!r}' for k, v in self.config_dict.items())
|
| 241 |
+
return f'ConfigWrapper({c})'
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
class ConfigWrapperStack:
|
| 245 |
+
"""A stack of `ConfigWrapper` instances."""
|
| 246 |
+
|
| 247 |
+
def __init__(self, config_wrapper: ConfigWrapper):
|
| 248 |
+
self._config_wrapper_stack: list[ConfigWrapper] = [config_wrapper]
|
| 249 |
+
|
| 250 |
+
@property
|
| 251 |
+
def tail(self) -> ConfigWrapper:
|
| 252 |
+
return self._config_wrapper_stack[-1]
|
| 253 |
+
|
| 254 |
+
@contextmanager
|
| 255 |
+
def push(self, config_wrapper: ConfigWrapper | ConfigDict | None):
|
| 256 |
+
if config_wrapper is None:
|
| 257 |
+
yield
|
| 258 |
+
return
|
| 259 |
+
|
| 260 |
+
if not isinstance(config_wrapper, ConfigWrapper):
|
| 261 |
+
config_wrapper = ConfigWrapper(config_wrapper, check=False)
|
| 262 |
+
|
| 263 |
+
self._config_wrapper_stack.append(config_wrapper)
|
| 264 |
+
try:
|
| 265 |
+
yield
|
| 266 |
+
finally:
|
| 267 |
+
self._config_wrapper_stack.pop()
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
config_defaults = ConfigDict(
|
| 271 |
+
title=None,
|
| 272 |
+
str_to_lower=False,
|
| 273 |
+
str_to_upper=False,
|
| 274 |
+
str_strip_whitespace=False,
|
| 275 |
+
str_min_length=0,
|
| 276 |
+
str_max_length=None,
|
| 277 |
+
# let the model / dataclass decide how to handle it
|
| 278 |
+
extra=None,
|
| 279 |
+
frozen=False,
|
| 280 |
+
populate_by_name=False,
|
| 281 |
+
use_enum_values=False,
|
| 282 |
+
validate_assignment=False,
|
| 283 |
+
arbitrary_types_allowed=False,
|
| 284 |
+
from_attributes=False,
|
| 285 |
+
loc_by_alias=True,
|
| 286 |
+
alias_generator=None,
|
| 287 |
+
model_title_generator=None,
|
| 288 |
+
field_title_generator=None,
|
| 289 |
+
ignored_types=(),
|
| 290 |
+
allow_inf_nan=True,
|
| 291 |
+
json_schema_extra=None,
|
| 292 |
+
strict=False,
|
| 293 |
+
revalidate_instances='never',
|
| 294 |
+
ser_json_timedelta='iso8601',
|
| 295 |
+
ser_json_temporal='iso8601',
|
| 296 |
+
val_temporal_unit='infer',
|
| 297 |
+
ser_json_bytes='utf8',
|
| 298 |
+
val_json_bytes='utf8',
|
| 299 |
+
ser_json_inf_nan='null',
|
| 300 |
+
validate_default=False,
|
| 301 |
+
validate_return=False,
|
| 302 |
+
protected_namespaces=('model_validate', 'model_dump'),
|
| 303 |
+
hide_input_in_errors=False,
|
| 304 |
+
json_encoders=None,
|
| 305 |
+
defer_build=False,
|
| 306 |
+
schema_generator=None,
|
| 307 |
+
plugin_settings=None,
|
| 308 |
+
json_schema_serialization_defaults_required=False,
|
| 309 |
+
json_schema_mode_override=None,
|
| 310 |
+
coerce_numbers_to_str=False,
|
| 311 |
+
regex_engine='rust-regex',
|
| 312 |
+
validation_error_cause=False,
|
| 313 |
+
use_attribute_docstrings=False,
|
| 314 |
+
cache_strings=True,
|
| 315 |
+
validate_by_alias=True,
|
| 316 |
+
validate_by_name=False,
|
| 317 |
+
serialize_by_alias=False,
|
| 318 |
+
url_preserve_empty_path=False,
|
| 319 |
+
polymorphic_serialization=False,
|
| 320 |
+
)
|
| 321 |
+
|
| 322 |
+
|
| 323 |
+
def prepare_config(config: ConfigDict | dict[str, Any] | type[Any] | None) -> ConfigDict:
|
| 324 |
+
"""Create a `ConfigDict` instance from an existing dict, a class (e.g. old class-based config) or None.
|
| 325 |
+
|
| 326 |
+
Args:
|
| 327 |
+
config: The input config.
|
| 328 |
+
|
| 329 |
+
Returns:
|
| 330 |
+
A ConfigDict object created from config.
|
| 331 |
+
"""
|
| 332 |
+
if config is None:
|
| 333 |
+
return ConfigDict()
|
| 334 |
+
|
| 335 |
+
if not isinstance(config, dict):
|
| 336 |
+
warnings.warn(DEPRECATION_MESSAGE, PydanticDeprecatedSince20, stacklevel=4)
|
| 337 |
+
config = {k: getattr(config, k) for k in dir(config) if not k.startswith('__')}
|
| 338 |
+
|
| 339 |
+
config_dict = cast(ConfigDict, config)
|
| 340 |
+
check_deprecated(config_dict)
|
| 341 |
+
return config_dict
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
config_keys = set(ConfigDict.__annotations__.keys())
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
V2_REMOVED_KEYS = {
|
| 348 |
+
'allow_mutation',
|
| 349 |
+
'error_msg_templates',
|
| 350 |
+
'fields',
|
| 351 |
+
'getter_dict',
|
| 352 |
+
'smart_union',
|
| 353 |
+
'underscore_attrs_are_private',
|
| 354 |
+
'json_loads',
|
| 355 |
+
'json_dumps',
|
| 356 |
+
'copy_on_model_validation',
|
| 357 |
+
'post_init_call',
|
| 358 |
+
}
|
| 359 |
+
V2_RENAMED_KEYS = {
|
| 360 |
+
'allow_population_by_field_name': 'validate_by_name',
|
| 361 |
+
'anystr_lower': 'str_to_lower',
|
| 362 |
+
'anystr_strip_whitespace': 'str_strip_whitespace',
|
| 363 |
+
'anystr_upper': 'str_to_upper',
|
| 364 |
+
'keep_untouched': 'ignored_types',
|
| 365 |
+
'max_anystr_length': 'str_max_length',
|
| 366 |
+
'min_anystr_length': 'str_min_length',
|
| 367 |
+
'orm_mode': 'from_attributes',
|
| 368 |
+
'schema_extra': 'json_schema_extra',
|
| 369 |
+
'validate_all': 'validate_default',
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
def check_deprecated(config_dict: ConfigDict) -> None:
|
| 374 |
+
"""Check for deprecated config keys and warn the user.
|
| 375 |
+
|
| 376 |
+
Args:
|
| 377 |
+
config_dict: The input config.
|
| 378 |
+
"""
|
| 379 |
+
deprecated_removed_keys = V2_REMOVED_KEYS & config_dict.keys()
|
| 380 |
+
deprecated_renamed_keys = V2_RENAMED_KEYS.keys() & config_dict.keys()
|
| 381 |
+
if deprecated_removed_keys or deprecated_renamed_keys:
|
| 382 |
+
renamings = {k: V2_RENAMED_KEYS[k] for k in sorted(deprecated_renamed_keys)}
|
| 383 |
+
renamed_bullets = [f'* {k!r} has been renamed to {v!r}' for k, v in renamings.items()]
|
| 384 |
+
removed_bullets = [f'* {k!r} has been removed' for k in sorted(deprecated_removed_keys)]
|
| 385 |
+
message = '\n'.join(['Valid config keys have changed in V2:'] + renamed_bullets + removed_bullets)
|
| 386 |
+
warnings.warn(message, UserWarning)
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_core_metadata.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations as _annotations
|
| 2 |
+
|
| 3 |
+
from typing import TYPE_CHECKING, Any, TypedDict, cast
|
| 4 |
+
from warnings import warn
|
| 5 |
+
|
| 6 |
+
if TYPE_CHECKING:
|
| 7 |
+
from ..config import JsonDict, JsonSchemaExtraCallable
|
| 8 |
+
from ._schema_generation_shared import (
|
| 9 |
+
GetJsonSchemaFunction,
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class CoreMetadata(TypedDict, total=False):
|
| 14 |
+
"""A `TypedDict` for holding the metadata dict of the schema.
|
| 15 |
+
|
| 16 |
+
Attributes:
|
| 17 |
+
pydantic_js_functions: List of JSON schema functions that resolve refs during application.
|
| 18 |
+
pydantic_js_annotation_functions: List of JSON schema functions that don't resolve refs during application.
|
| 19 |
+
pydantic_js_prefer_positional_arguments: Whether JSON schema generator will
|
| 20 |
+
prefer positional over keyword arguments for an 'arguments' schema.
|
| 21 |
+
custom validation function. Only applies to before, plain, and wrap validators.
|
| 22 |
+
pydantic_js_updates: key / value pair updates to apply to the JSON schema for a type.
|
| 23 |
+
pydantic_js_extra: WIP, either key/value pair updates to apply to the JSON schema, or a custom callable.
|
| 24 |
+
pydantic_internal_union_tag_key: Used internally by the `Tag` metadata to specify the tag used for a discriminated union.
|
| 25 |
+
pydantic_internal_union_discriminator: Used internally to specify the discriminator value for a discriminated union
|
| 26 |
+
when the discriminator was applied to a `'definition-ref'` schema, and that reference was missing at the time
|
| 27 |
+
of the annotation application.
|
| 28 |
+
|
| 29 |
+
TODO: Perhaps we should move this structure to pydantic-core. At the moment, though,
|
| 30 |
+
it's easier to iterate on if we leave it in pydantic until we feel there is a semi-stable API.
|
| 31 |
+
|
| 32 |
+
TODO: It's unfortunate how functionally oriented JSON schema generation is, especially that which occurs during
|
| 33 |
+
the core schema generation process. It's inevitable that we need to store some json schema related information
|
| 34 |
+
on core schemas, given that we generate JSON schemas directly from core schemas. That being said, debugging related
|
| 35 |
+
issues is quite difficult when JSON schema information is disguised via dynamically defined functions.
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
pydantic_js_functions: list[GetJsonSchemaFunction]
|
| 39 |
+
pydantic_js_annotation_functions: list[GetJsonSchemaFunction]
|
| 40 |
+
pydantic_js_prefer_positional_arguments: bool
|
| 41 |
+
pydantic_js_updates: JsonDict
|
| 42 |
+
pydantic_js_extra: JsonDict | JsonSchemaExtraCallable
|
| 43 |
+
pydantic_internal_union_tag_key: str
|
| 44 |
+
pydantic_internal_union_discriminator: str
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def update_core_metadata(
|
| 48 |
+
core_metadata: Any,
|
| 49 |
+
/,
|
| 50 |
+
*,
|
| 51 |
+
pydantic_js_functions: list[GetJsonSchemaFunction] | None = None,
|
| 52 |
+
pydantic_js_annotation_functions: list[GetJsonSchemaFunction] | None = None,
|
| 53 |
+
pydantic_js_updates: JsonDict | None = None,
|
| 54 |
+
pydantic_js_extra: JsonDict | JsonSchemaExtraCallable | None = None,
|
| 55 |
+
) -> None:
|
| 56 |
+
from ..json_schema import PydanticJsonSchemaWarning
|
| 57 |
+
|
| 58 |
+
"""Update CoreMetadata instance in place. When we make modifications in this function, they
|
| 59 |
+
take effect on the `core_metadata` reference passed in as the first (and only) positional argument.
|
| 60 |
+
|
| 61 |
+
First, cast to `CoreMetadata`, then finish with a cast to `dict[str, Any]` for core schema compatibility.
|
| 62 |
+
We do this here, instead of before / after each call to this function so that this typing hack
|
| 63 |
+
can be easily removed if/when we move `CoreMetadata` to `pydantic-core`.
|
| 64 |
+
|
| 65 |
+
For parameter descriptions, see `CoreMetadata` above.
|
| 66 |
+
"""
|
| 67 |
+
core_metadata = cast(CoreMetadata, core_metadata)
|
| 68 |
+
|
| 69 |
+
if pydantic_js_functions:
|
| 70 |
+
core_metadata.setdefault('pydantic_js_functions', []).extend(pydantic_js_functions)
|
| 71 |
+
|
| 72 |
+
if pydantic_js_annotation_functions:
|
| 73 |
+
core_metadata.setdefault('pydantic_js_annotation_functions', []).extend(pydantic_js_annotation_functions)
|
| 74 |
+
|
| 75 |
+
if pydantic_js_updates:
|
| 76 |
+
if (existing_updates := core_metadata.get('pydantic_js_updates')) is not None:
|
| 77 |
+
core_metadata['pydantic_js_updates'] = {**existing_updates, **pydantic_js_updates}
|
| 78 |
+
else:
|
| 79 |
+
core_metadata['pydantic_js_updates'] = pydantic_js_updates
|
| 80 |
+
|
| 81 |
+
if pydantic_js_extra is not None:
|
| 82 |
+
existing_pydantic_js_extra = core_metadata.get('pydantic_js_extra')
|
| 83 |
+
if existing_pydantic_js_extra is None:
|
| 84 |
+
core_metadata['pydantic_js_extra'] = pydantic_js_extra
|
| 85 |
+
if isinstance(existing_pydantic_js_extra, dict):
|
| 86 |
+
if isinstance(pydantic_js_extra, dict):
|
| 87 |
+
core_metadata['pydantic_js_extra'] = {**existing_pydantic_js_extra, **pydantic_js_extra}
|
| 88 |
+
if callable(pydantic_js_extra):
|
| 89 |
+
warn(
|
| 90 |
+
'Composing `dict` and `callable` type `json_schema_extra` is not supported.'
|
| 91 |
+
'The `callable` type is being ignored.'
|
| 92 |
+
"If you'd like support for this behavior, please open an issue on pydantic.",
|
| 93 |
+
PydanticJsonSchemaWarning,
|
| 94 |
+
)
|
| 95 |
+
if callable(existing_pydantic_js_extra):
|
| 96 |
+
# if ever there's a case of a callable, we'll just keep the last json schema extra spec
|
| 97 |
+
core_metadata['pydantic_js_extra'] = pydantic_js_extra
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_core_utils.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import inspect
|
| 4 |
+
from collections.abc import Mapping, Sequence
|
| 5 |
+
from typing import TYPE_CHECKING, Any, Union
|
| 6 |
+
|
| 7 |
+
from pydantic_core import CoreSchema, core_schema
|
| 8 |
+
from typing_extensions import TypeGuard, get_args, get_origin
|
| 9 |
+
from typing_inspection import typing_objects
|
| 10 |
+
|
| 11 |
+
from . import _repr
|
| 12 |
+
from ._typing_extra import is_generic_alias
|
| 13 |
+
|
| 14 |
+
if TYPE_CHECKING:
|
| 15 |
+
from rich.console import Console
|
| 16 |
+
|
| 17 |
+
AnyFunctionSchema = Union[
|
| 18 |
+
core_schema.AfterValidatorFunctionSchema,
|
| 19 |
+
core_schema.BeforeValidatorFunctionSchema,
|
| 20 |
+
core_schema.WrapValidatorFunctionSchema,
|
| 21 |
+
core_schema.PlainValidatorFunctionSchema,
|
| 22 |
+
]
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
FunctionSchemaWithInnerSchema = Union[
|
| 26 |
+
core_schema.AfterValidatorFunctionSchema,
|
| 27 |
+
core_schema.BeforeValidatorFunctionSchema,
|
| 28 |
+
core_schema.WrapValidatorFunctionSchema,
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
CoreSchemaField = Union[
|
| 32 |
+
core_schema.ModelField, core_schema.DataclassField, core_schema.TypedDictField, core_schema.ComputedField
|
| 33 |
+
]
|
| 34 |
+
CoreSchemaOrField = Union[core_schema.CoreSchema, CoreSchemaField]
|
| 35 |
+
|
| 36 |
+
_CORE_SCHEMA_FIELD_TYPES = {'typed-dict-field', 'dataclass-field', 'model-field', 'computed-field'}
|
| 37 |
+
_FUNCTION_WITH_INNER_SCHEMA_TYPES = {'function-before', 'function-after', 'function-wrap'}
|
| 38 |
+
_LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES = {'list', 'set', 'frozenset'}
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def is_core_schema(
|
| 42 |
+
schema: CoreSchemaOrField,
|
| 43 |
+
) -> TypeGuard[CoreSchema]:
|
| 44 |
+
return schema['type'] not in _CORE_SCHEMA_FIELD_TYPES
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def is_core_schema_field(
|
| 48 |
+
schema: CoreSchemaOrField,
|
| 49 |
+
) -> TypeGuard[CoreSchemaField]:
|
| 50 |
+
return schema['type'] in _CORE_SCHEMA_FIELD_TYPES
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def is_function_with_inner_schema(
|
| 54 |
+
schema: CoreSchemaOrField,
|
| 55 |
+
) -> TypeGuard[FunctionSchemaWithInnerSchema]:
|
| 56 |
+
return schema['type'] in _FUNCTION_WITH_INNER_SCHEMA_TYPES
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def is_list_like_schema_with_items_schema(
|
| 60 |
+
schema: CoreSchema,
|
| 61 |
+
) -> TypeGuard[core_schema.ListSchema | core_schema.SetSchema | core_schema.FrozenSetSchema]:
|
| 62 |
+
return schema['type'] in _LIST_LIKE_SCHEMA_WITH_ITEMS_TYPES
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def get_type_ref(type_: Any, args_override: tuple[type[Any], ...] | None = None) -> str:
|
| 66 |
+
"""Produces the ref to be used for this type by pydantic_core's core schemas.
|
| 67 |
+
|
| 68 |
+
This `args_override` argument was added for the purpose of creating valid recursive references
|
| 69 |
+
when creating generic models without needing to create a concrete class.
|
| 70 |
+
"""
|
| 71 |
+
origin = get_origin(type_) or type_
|
| 72 |
+
|
| 73 |
+
args = get_args(type_) if is_generic_alias(type_) else (args_override or ())
|
| 74 |
+
generic_metadata = getattr(type_, '__pydantic_generic_metadata__', None)
|
| 75 |
+
if generic_metadata:
|
| 76 |
+
origin = generic_metadata['origin'] or origin
|
| 77 |
+
args = generic_metadata['args'] or args
|
| 78 |
+
|
| 79 |
+
module_name = getattr(origin, '__module__', '<No __module__>')
|
| 80 |
+
if typing_objects.is_typealiastype(origin):
|
| 81 |
+
type_ref = f'{module_name}.{origin.__name__}:{id(origin)}'
|
| 82 |
+
else:
|
| 83 |
+
try:
|
| 84 |
+
qualname = getattr(origin, '__qualname__', f'<No __qualname__: {origin}>')
|
| 85 |
+
except Exception:
|
| 86 |
+
qualname = getattr(origin, '__qualname__', '<No __qualname__>')
|
| 87 |
+
type_ref = f'{module_name}.{qualname}:{id(origin)}'
|
| 88 |
+
|
| 89 |
+
arg_refs: list[str] = []
|
| 90 |
+
for arg in args:
|
| 91 |
+
if isinstance(arg, str):
|
| 92 |
+
# Handle string literals as a special case; we may be able to remove this special handling if we
|
| 93 |
+
# wrap them in a ForwardRef at some point.
|
| 94 |
+
arg_ref = f'{arg}:str-{id(arg)}'
|
| 95 |
+
else:
|
| 96 |
+
arg_ref = f'{_repr.display_as_type(arg)}:{id(arg)}'
|
| 97 |
+
arg_refs.append(arg_ref)
|
| 98 |
+
if arg_refs:
|
| 99 |
+
type_ref = f'{type_ref}[{",".join(arg_refs)}]'
|
| 100 |
+
return type_ref
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def get_ref(s: core_schema.CoreSchema) -> None | str:
|
| 104 |
+
"""Get the ref from the schema if it has one.
|
| 105 |
+
This exists just for type checking to work correctly.
|
| 106 |
+
"""
|
| 107 |
+
return s.get('ref', None)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _clean_schema_for_pretty_print(obj: Any, strip_metadata: bool = True) -> Any: # pragma: no cover
|
| 111 |
+
"""A utility function to remove irrelevant information from a core schema."""
|
| 112 |
+
if isinstance(obj, Mapping):
|
| 113 |
+
new_dct = {}
|
| 114 |
+
for k, v in obj.items():
|
| 115 |
+
if k == 'metadata' and strip_metadata:
|
| 116 |
+
new_metadata = {}
|
| 117 |
+
|
| 118 |
+
for meta_k, meta_v in v.items():
|
| 119 |
+
if meta_k in ('pydantic_js_functions', 'pydantic_js_annotation_functions'):
|
| 120 |
+
new_metadata['js_metadata'] = '<stripped>'
|
| 121 |
+
else:
|
| 122 |
+
new_metadata[meta_k] = _clean_schema_for_pretty_print(meta_v, strip_metadata=strip_metadata)
|
| 123 |
+
|
| 124 |
+
if list(new_metadata.keys()) == ['js_metadata']:
|
| 125 |
+
new_metadata = {'<stripped>'}
|
| 126 |
+
|
| 127 |
+
new_dct[k] = new_metadata
|
| 128 |
+
# Remove some defaults:
|
| 129 |
+
elif k in ('custom_init', 'root_model') and not v:
|
| 130 |
+
continue
|
| 131 |
+
else:
|
| 132 |
+
new_dct[k] = _clean_schema_for_pretty_print(v, strip_metadata=strip_metadata)
|
| 133 |
+
|
| 134 |
+
return new_dct
|
| 135 |
+
elif isinstance(obj, Sequence) and not isinstance(obj, str):
|
| 136 |
+
return [_clean_schema_for_pretty_print(v, strip_metadata=strip_metadata) for v in obj]
|
| 137 |
+
else:
|
| 138 |
+
return obj
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def pretty_print_core_schema(
|
| 142 |
+
val: Any,
|
| 143 |
+
*,
|
| 144 |
+
console: Console | None = None,
|
| 145 |
+
max_depth: int | None = None,
|
| 146 |
+
strip_metadata: bool = True,
|
| 147 |
+
) -> None: # pragma: no cover
|
| 148 |
+
"""Pretty-print a core schema using the `rich` library.
|
| 149 |
+
|
| 150 |
+
Args:
|
| 151 |
+
val: The core schema to print, or a Pydantic model/dataclass/type adapter
|
| 152 |
+
(in which case the cached core schema is fetched and printed).
|
| 153 |
+
console: A rich console to use when printing. Defaults to the global rich console instance.
|
| 154 |
+
max_depth: The number of nesting levels which may be printed.
|
| 155 |
+
strip_metadata: Whether to strip metadata in the output. If `True` any known core metadata
|
| 156 |
+
attributes will be stripped (but custom attributes are kept). Defaults to `True`.
|
| 157 |
+
"""
|
| 158 |
+
# lazy import:
|
| 159 |
+
from rich.pretty import pprint
|
| 160 |
+
|
| 161 |
+
# circ. imports:
|
| 162 |
+
from pydantic import BaseModel, TypeAdapter
|
| 163 |
+
from pydantic.dataclasses import is_pydantic_dataclass
|
| 164 |
+
|
| 165 |
+
if (inspect.isclass(val) and issubclass(val, BaseModel)) or is_pydantic_dataclass(val):
|
| 166 |
+
val = val.__pydantic_core_schema__
|
| 167 |
+
if isinstance(val, TypeAdapter):
|
| 168 |
+
val = val.core_schema
|
| 169 |
+
cleaned_schema = _clean_schema_for_pretty_print(val, strip_metadata=strip_metadata)
|
| 170 |
+
|
| 171 |
+
pprint(cleaned_schema, console=console, max_depth=max_depth)
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
pps = pretty_print_core_schema
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_dataclasses.py
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Private logic for creating pydantic dataclasses."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations as _annotations
|
| 4 |
+
|
| 5 |
+
import copy
|
| 6 |
+
import dataclasses
|
| 7 |
+
import sys
|
| 8 |
+
import warnings
|
| 9 |
+
from collections.abc import Generator
|
| 10 |
+
from contextlib import contextmanager
|
| 11 |
+
from functools import partial
|
| 12 |
+
from typing import TYPE_CHECKING, Any, ClassVar, Protocol, cast
|
| 13 |
+
|
| 14 |
+
from pydantic_core import (
|
| 15 |
+
ArgsKwargs,
|
| 16 |
+
SchemaSerializer,
|
| 17 |
+
SchemaValidator,
|
| 18 |
+
core_schema,
|
| 19 |
+
)
|
| 20 |
+
from typing_extensions import TypeAlias, TypeIs
|
| 21 |
+
|
| 22 |
+
from ..errors import PydanticUndefinedAnnotation
|
| 23 |
+
from ..fields import FieldInfo
|
| 24 |
+
from ..plugin._schema_validator import PluggableSchemaValidator, create_schema_validator
|
| 25 |
+
from ..warnings import PydanticDeprecatedSince20
|
| 26 |
+
from . import _config, _decorators
|
| 27 |
+
from ._fields import collect_dataclass_fields
|
| 28 |
+
from ._generate_schema import GenerateSchema, InvalidSchemaError
|
| 29 |
+
from ._generics import get_standard_typevars_map
|
| 30 |
+
from ._mock_val_ser import set_dataclass_mocks
|
| 31 |
+
from ._namespace_utils import NsResolver
|
| 32 |
+
from ._signature import generate_pydantic_signature
|
| 33 |
+
from ._utils import LazyClassAttribute
|
| 34 |
+
|
| 35 |
+
if TYPE_CHECKING:
|
| 36 |
+
from _typeshed import DataclassInstance as StandardDataclass
|
| 37 |
+
|
| 38 |
+
from ..config import ConfigDict
|
| 39 |
+
|
| 40 |
+
class PydanticDataclass(StandardDataclass, Protocol):
|
| 41 |
+
"""A protocol containing attributes only available once a class has been decorated as a Pydantic dataclass.
|
| 42 |
+
|
| 43 |
+
Attributes:
|
| 44 |
+
__pydantic_config__: Pydantic-specific configuration settings for the dataclass.
|
| 45 |
+
__pydantic_complete__: Whether dataclass building is completed, or if there are still undefined fields.
|
| 46 |
+
__pydantic_core_schema__: The pydantic-core schema used to build the SchemaValidator and SchemaSerializer.
|
| 47 |
+
__pydantic_decorators__: Metadata containing the decorators defined on the dataclass.
|
| 48 |
+
__pydantic_fields__: Metadata about the fields defined on the dataclass.
|
| 49 |
+
__pydantic_serializer__: The pydantic-core SchemaSerializer used to dump instances of the dataclass.
|
| 50 |
+
__pydantic_validator__: The pydantic-core SchemaValidator used to validate instances of the dataclass.
|
| 51 |
+
"""
|
| 52 |
+
|
| 53 |
+
__pydantic_config__: ClassVar[ConfigDict]
|
| 54 |
+
__pydantic_complete__: ClassVar[bool]
|
| 55 |
+
__pydantic_core_schema__: ClassVar[core_schema.CoreSchema]
|
| 56 |
+
__pydantic_decorators__: ClassVar[_decorators.DecoratorInfos]
|
| 57 |
+
__pydantic_fields__: ClassVar[dict[str, FieldInfo]]
|
| 58 |
+
__pydantic_serializer__: ClassVar[SchemaSerializer]
|
| 59 |
+
__pydantic_validator__: ClassVar[SchemaValidator | PluggableSchemaValidator]
|
| 60 |
+
|
| 61 |
+
@classmethod
|
| 62 |
+
def __pydantic_fields_complete__(cls) -> bool: ...
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def set_dataclass_fields(
|
| 66 |
+
cls: type[StandardDataclass],
|
| 67 |
+
config_wrapper: _config.ConfigWrapper,
|
| 68 |
+
ns_resolver: NsResolver | None = None,
|
| 69 |
+
) -> None:
|
| 70 |
+
"""Collect and set `cls.__pydantic_fields__`.
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
cls: The class.
|
| 74 |
+
config_wrapper: The config wrapper instance.
|
| 75 |
+
ns_resolver: Namespace resolver to use when getting dataclass annotations.
|
| 76 |
+
"""
|
| 77 |
+
typevars_map = get_standard_typevars_map(cls)
|
| 78 |
+
fields = collect_dataclass_fields(
|
| 79 |
+
cls, ns_resolver=ns_resolver, typevars_map=typevars_map, config_wrapper=config_wrapper
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
cls.__pydantic_fields__ = fields # type: ignore
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def complete_dataclass(
|
| 86 |
+
cls: type[Any],
|
| 87 |
+
config_wrapper: _config.ConfigWrapper,
|
| 88 |
+
*,
|
| 89 |
+
raise_errors: bool = True,
|
| 90 |
+
ns_resolver: NsResolver | None = None,
|
| 91 |
+
_force_build: bool = False,
|
| 92 |
+
) -> bool:
|
| 93 |
+
"""Finish building a pydantic dataclass.
|
| 94 |
+
|
| 95 |
+
This logic is called on a class which has already been wrapped in `dataclasses.dataclass()`.
|
| 96 |
+
|
| 97 |
+
This is somewhat analogous to `pydantic._internal._model_construction.complete_model_class`.
|
| 98 |
+
|
| 99 |
+
Args:
|
| 100 |
+
cls: The class.
|
| 101 |
+
config_wrapper: The config wrapper instance.
|
| 102 |
+
raise_errors: Whether to raise errors, defaults to `True`.
|
| 103 |
+
ns_resolver: The namespace resolver instance to use when collecting dataclass fields
|
| 104 |
+
and during schema building.
|
| 105 |
+
_force_build: Whether to force building the dataclass, no matter if
|
| 106 |
+
[`defer_build`][pydantic.config.ConfigDict.defer_build] is set.
|
| 107 |
+
|
| 108 |
+
Returns:
|
| 109 |
+
`True` if building a pydantic dataclass is successfully completed, `False` otherwise.
|
| 110 |
+
|
| 111 |
+
Raises:
|
| 112 |
+
PydanticUndefinedAnnotation: If `raise_error` is `True` and there is an undefined annotations.
|
| 113 |
+
"""
|
| 114 |
+
original_init = cls.__init__
|
| 115 |
+
|
| 116 |
+
# dataclass.__init__ must be defined here so its `__qualname__` can be changed since functions can't be copied,
|
| 117 |
+
# and so that the mock validator is used if building was deferred:
|
| 118 |
+
def __init__(__dataclass_self__: PydanticDataclass, *args: Any, **kwargs: Any) -> None:
|
| 119 |
+
__tracebackhide__ = True
|
| 120 |
+
s = __dataclass_self__
|
| 121 |
+
s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
|
| 122 |
+
|
| 123 |
+
__init__.__qualname__ = f'{cls.__qualname__}.__init__'
|
| 124 |
+
|
| 125 |
+
cls.__init__ = __init__ # type: ignore
|
| 126 |
+
cls.__pydantic_config__ = config_wrapper.config_dict # type: ignore
|
| 127 |
+
|
| 128 |
+
set_dataclass_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver)
|
| 129 |
+
|
| 130 |
+
if not _force_build and config_wrapper.defer_build:
|
| 131 |
+
set_dataclass_mocks(cls)
|
| 132 |
+
return False
|
| 133 |
+
|
| 134 |
+
if hasattr(cls, '__post_init_post_parse__'):
|
| 135 |
+
warnings.warn(
|
| 136 |
+
'Support for `__post_init_post_parse__` has been dropped, the method will not be called',
|
| 137 |
+
PydanticDeprecatedSince20,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
typevars_map = get_standard_typevars_map(cls)
|
| 141 |
+
gen_schema = GenerateSchema(
|
| 142 |
+
config_wrapper,
|
| 143 |
+
ns_resolver=ns_resolver,
|
| 144 |
+
typevars_map=typevars_map,
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
# set __signature__ attr only for the class, but not for its instances
|
| 148 |
+
# (because instances can define `__call__`, and `inspect.signature` shouldn't
|
| 149 |
+
# use the `__signature__` attribute and instead generate from `__call__`).
|
| 150 |
+
cls.__signature__ = LazyClassAttribute(
|
| 151 |
+
'__signature__',
|
| 152 |
+
partial(
|
| 153 |
+
generate_pydantic_signature,
|
| 154 |
+
# It's important that we reference the `original_init` here,
|
| 155 |
+
# as it is the one synthesized by the stdlib `dataclass` module:
|
| 156 |
+
init=original_init,
|
| 157 |
+
fields=cls.__pydantic_fields__, # type: ignore
|
| 158 |
+
validate_by_name=config_wrapper.validate_by_name,
|
| 159 |
+
extra=config_wrapper.extra,
|
| 160 |
+
is_dataclass=True,
|
| 161 |
+
),
|
| 162 |
+
)
|
| 163 |
+
|
| 164 |
+
try:
|
| 165 |
+
schema = gen_schema.generate_schema(cls)
|
| 166 |
+
except PydanticUndefinedAnnotation as e:
|
| 167 |
+
if raise_errors:
|
| 168 |
+
raise
|
| 169 |
+
set_dataclass_mocks(cls, f'`{e.name}`')
|
| 170 |
+
return False
|
| 171 |
+
|
| 172 |
+
core_config = config_wrapper.core_config(title=cls.__name__)
|
| 173 |
+
|
| 174 |
+
try:
|
| 175 |
+
schema = gen_schema.clean_schema(schema)
|
| 176 |
+
except InvalidSchemaError:
|
| 177 |
+
set_dataclass_mocks(cls)
|
| 178 |
+
return False
|
| 179 |
+
|
| 180 |
+
# We are about to set all the remaining required properties expected for this cast;
|
| 181 |
+
# __pydantic_decorators__ and __pydantic_fields__ should already be set
|
| 182 |
+
cls = cast('type[PydanticDataclass]', cls)
|
| 183 |
+
|
| 184 |
+
cls.__pydantic_core_schema__ = schema
|
| 185 |
+
cls.__pydantic_validator__ = create_schema_validator(
|
| 186 |
+
schema, cls, cls.__module__, cls.__qualname__, 'dataclass', core_config, config_wrapper.plugin_settings
|
| 187 |
+
)
|
| 188 |
+
cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config)
|
| 189 |
+
cls.__pydantic_complete__ = True
|
| 190 |
+
return True
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def is_stdlib_dataclass(cls: type[Any], /) -> TypeIs[type[StandardDataclass]]:
|
| 194 |
+
"""Returns `True` if the class is a stdlib dataclass and *not* a Pydantic dataclass.
|
| 195 |
+
|
| 196 |
+
Unlike the stdlib `dataclasses.is_dataclass()` function, this does *not* include subclasses
|
| 197 |
+
of a dataclass that are themselves not dataclasses.
|
| 198 |
+
|
| 199 |
+
Args:
|
| 200 |
+
cls: The class.
|
| 201 |
+
|
| 202 |
+
Returns:
|
| 203 |
+
`True` if the class is a stdlib dataclass, `False` otherwise.
|
| 204 |
+
"""
|
| 205 |
+
return '__dataclass_fields__' in cls.__dict__ and not hasattr(cls, '__pydantic_validator__')
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def as_dataclass_field(pydantic_field: FieldInfo) -> dataclasses.Field[Any]:
|
| 209 |
+
field_args: dict[str, Any] = {'default': pydantic_field}
|
| 210 |
+
|
| 211 |
+
# Needed because if `doc` is set, the dataclass slots will be a dict (field name -> doc) instead of a tuple:
|
| 212 |
+
if sys.version_info >= (3, 14) and pydantic_field.description is not None:
|
| 213 |
+
field_args['doc'] = pydantic_field.description
|
| 214 |
+
|
| 215 |
+
# Needed as the stdlib dataclass module processes kw_only in a specific way during class construction:
|
| 216 |
+
if sys.version_info >= (3, 10) and pydantic_field.kw_only is not None:
|
| 217 |
+
field_args['kw_only'] = pydantic_field.kw_only
|
| 218 |
+
|
| 219 |
+
# Needed as the stdlib dataclass modules generates `__repr__()` during class construction:
|
| 220 |
+
if pydantic_field.repr is not True:
|
| 221 |
+
field_args['repr'] = pydantic_field.repr
|
| 222 |
+
|
| 223 |
+
return dataclasses.field(**field_args)
|
| 224 |
+
|
| 225 |
+
|
| 226 |
+
DcFields: TypeAlias = dict[str, dataclasses.Field[Any]]
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
@contextmanager
|
| 230 |
+
def patch_base_fields(cls: type[Any]) -> Generator[None]:
|
| 231 |
+
"""Temporarily patch the stdlib dataclasses bases of `cls` if the Pydantic `Field()` function is used.
|
| 232 |
+
|
| 233 |
+
When creating a Pydantic dataclass, it is possible to inherit from stdlib dataclasses, where
|
| 234 |
+
the Pydantic `Field()` function is used. To create this Pydantic dataclass, we first apply
|
| 235 |
+
the stdlib `@dataclass` decorator on it. During the construction of the stdlib dataclass,
|
| 236 |
+
the `kw_only` and `repr` field arguments need to be understood by the stdlib *during* the
|
| 237 |
+
dataclass construction. To do so, we temporarily patch the fields dictionary of the affected
|
| 238 |
+
bases.
|
| 239 |
+
|
| 240 |
+
For instance, with the following example:
|
| 241 |
+
|
| 242 |
+
```python {test="skip" lint="skip"}
|
| 243 |
+
import dataclasses as stdlib_dc
|
| 244 |
+
|
| 245 |
+
import pydantic
|
| 246 |
+
import pydantic.dataclasses as pydantic_dc
|
| 247 |
+
|
| 248 |
+
@stdlib_dc.dataclass
|
| 249 |
+
class A:
|
| 250 |
+
a: int = pydantic.Field(repr=False)
|
| 251 |
+
|
| 252 |
+
# Notice that the `repr` attribute of the dataclass field is `True`:
|
| 253 |
+
A.__dataclass_fields__['a']
|
| 254 |
+
#> dataclass.Field(default=FieldInfo(repr=False), repr=True, ...)
|
| 255 |
+
|
| 256 |
+
@pydantic_dc.dataclass
|
| 257 |
+
class B(A):
|
| 258 |
+
b: int = pydantic.Field(repr=False)
|
| 259 |
+
```
|
| 260 |
+
|
| 261 |
+
When passing `B` to the stdlib `@dataclass` decorator, it will look for fields in the parent classes
|
| 262 |
+
and reuse them directly. When this context manager is active, `A` will be temporarily patched to be
|
| 263 |
+
equivalent to:
|
| 264 |
+
|
| 265 |
+
```python {test="skip" lint="skip"}
|
| 266 |
+
@stdlib_dc.dataclass
|
| 267 |
+
class A:
|
| 268 |
+
a: int = stdlib_dc.field(default=Field(repr=False), repr=False)
|
| 269 |
+
```
|
| 270 |
+
|
| 271 |
+
!!! note
|
| 272 |
+
This is only applied to the bases of `cls`, and not `cls` itself. The reason is that the Pydantic
|
| 273 |
+
dataclass decorator "owns" `cls` (in the previous example, `B`). As such, we instead modify the fields
|
| 274 |
+
directly (in the previous example, we simply do `setattr(B, 'b', as_dataclass_field(pydantic_field))`).
|
| 275 |
+
|
| 276 |
+
!!! note
|
| 277 |
+
This approach is far from ideal, and can probably be the source of unwanted side effects/race conditions.
|
| 278 |
+
The previous implemented approach was mutating the `__annotations__` dict of `cls`, which is no longer a
|
| 279 |
+
safe operation in Python 3.14+, and resulted in unexpected behavior with field ordering anyway.
|
| 280 |
+
"""
|
| 281 |
+
# A list of two-tuples, the first element being a reference to the
|
| 282 |
+
# dataclass fields dictionary, the second element being a mapping between
|
| 283 |
+
# the field names that were modified, and their original `Field`:
|
| 284 |
+
original_fields_list: list[tuple[DcFields, DcFields]] = []
|
| 285 |
+
|
| 286 |
+
for base in cls.__mro__[1:]:
|
| 287 |
+
dc_fields: dict[str, dataclasses.Field[Any]] = base.__dict__.get('__dataclass_fields__', {})
|
| 288 |
+
dc_fields_with_pydantic_field_defaults = {
|
| 289 |
+
field_name: field
|
| 290 |
+
for field_name, field in dc_fields.items()
|
| 291 |
+
if isinstance(field.default, FieldInfo)
|
| 292 |
+
# Only do the patching if one of the affected attributes is set:
|
| 293 |
+
and (field.default.description is not None or field.default.kw_only or field.default.repr is not True)
|
| 294 |
+
}
|
| 295 |
+
if dc_fields_with_pydantic_field_defaults:
|
| 296 |
+
original_fields_list.append((dc_fields, dc_fields_with_pydantic_field_defaults))
|
| 297 |
+
for field_name, field in dc_fields_with_pydantic_field_defaults.items():
|
| 298 |
+
default = cast(FieldInfo, field.default)
|
| 299 |
+
# `dataclasses.Field` isn't documented as working with `copy.copy()`.
|
| 300 |
+
# It is a class with `__slots__`, so should work (and we hope for the best):
|
| 301 |
+
new_dc_field = copy.copy(field)
|
| 302 |
+
# For base fields, no need to set `doc` from `FieldInfo.description`, this is only relevant
|
| 303 |
+
# for the class under construction and handled in `as_dataclass_field()`.
|
| 304 |
+
if sys.version_info >= (3, 10) and default.kw_only:
|
| 305 |
+
new_dc_field.kw_only = True
|
| 306 |
+
if default.repr is not True:
|
| 307 |
+
new_dc_field.repr = default.repr
|
| 308 |
+
dc_fields[field_name] = new_dc_field
|
| 309 |
+
|
| 310 |
+
try:
|
| 311 |
+
yield
|
| 312 |
+
finally:
|
| 313 |
+
for fields, original_fields in original_fields_list:
|
| 314 |
+
for field_name, original_field in original_fields.items():
|
| 315 |
+
fields[field_name] = original_field
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_decorators.py
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Logic related to validators applied to models etc. via the `@field_validator` and `@model_validator` decorators."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations as _annotations
|
| 4 |
+
|
| 5 |
+
import types
|
| 6 |
+
from collections import deque
|
| 7 |
+
from collections.abc import Iterable
|
| 8 |
+
from copy import copy
|
| 9 |
+
from dataclasses import dataclass, field
|
| 10 |
+
from functools import cached_property, partial, partialmethod
|
| 11 |
+
from inspect import Parameter, Signature, isdatadescriptor, ismethoddescriptor
|
| 12 |
+
from itertools import islice
|
| 13 |
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, Literal, TypeVar, Union
|
| 14 |
+
|
| 15 |
+
from pydantic_core import PydanticUndefined, PydanticUndefinedType, core_schema
|
| 16 |
+
from typing_extensions import Self, TypeAlias, is_typeddict
|
| 17 |
+
|
| 18 |
+
from ..errors import PydanticUserError
|
| 19 |
+
from ._core_utils import get_type_ref
|
| 20 |
+
from ._internal_dataclass import slots_true
|
| 21 |
+
from ._namespace_utils import GlobalsNamespace, MappingNamespace
|
| 22 |
+
from ._typing_extra import get_function_type_hints, signature_no_eval
|
| 23 |
+
from ._utils import can_be_positional
|
| 24 |
+
|
| 25 |
+
if TYPE_CHECKING:
|
| 26 |
+
from ..fields import ComputedFieldInfo
|
| 27 |
+
from ..functional_validators import FieldValidatorModes
|
| 28 |
+
from ._config import ConfigWrapper
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass(**slots_true)
|
| 32 |
+
class ValidatorDecoratorInfo:
|
| 33 |
+
"""A container for data from `@validator` so that we can access it
|
| 34 |
+
while building the pydantic-core schema.
|
| 35 |
+
|
| 36 |
+
Attributes:
|
| 37 |
+
decorator_repr: A class variable representing the decorator string, '@validator'.
|
| 38 |
+
fields: A tuple of field names the validator should be called on.
|
| 39 |
+
mode: The proposed validator mode.
|
| 40 |
+
each_item: For complex objects (sets, lists etc.) whether to validate individual
|
| 41 |
+
elements rather than the whole object.
|
| 42 |
+
always: Whether this method and other validators should be called even if the value is missing.
|
| 43 |
+
check_fields: Whether to check that the fields actually exist on the model.
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
decorator_repr: ClassVar[str] = '@validator'
|
| 47 |
+
|
| 48 |
+
fields: tuple[str, ...]
|
| 49 |
+
mode: Literal['before', 'after']
|
| 50 |
+
each_item: bool
|
| 51 |
+
always: bool
|
| 52 |
+
check_fields: bool | None
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@dataclass(**slots_true)
|
| 56 |
+
class FieldValidatorDecoratorInfo:
|
| 57 |
+
"""A container for data from `@field_validator` so that we can access it
|
| 58 |
+
while building the pydantic-core schema.
|
| 59 |
+
|
| 60 |
+
Attributes:
|
| 61 |
+
decorator_repr: A class variable representing the decorator string, '@field_validator'.
|
| 62 |
+
fields: A tuple of field names the validator should be called on.
|
| 63 |
+
mode: The proposed validator mode.
|
| 64 |
+
check_fields: Whether to check that the fields actually exist on the model.
|
| 65 |
+
json_schema_input_type: The input type of the function. This is only used to generate
|
| 66 |
+
the appropriate JSON Schema (in validation mode) and can only specified
|
| 67 |
+
when `mode` is either `'before'`, `'plain'` or `'wrap'`.
|
| 68 |
+
"""
|
| 69 |
+
|
| 70 |
+
decorator_repr: ClassVar[str] = '@field_validator'
|
| 71 |
+
|
| 72 |
+
fields: tuple[str, ...]
|
| 73 |
+
mode: FieldValidatorModes
|
| 74 |
+
check_fields: bool | None
|
| 75 |
+
json_schema_input_type: Any
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@dataclass(**slots_true)
|
| 79 |
+
class RootValidatorDecoratorInfo:
|
| 80 |
+
"""A container for data from `@root_validator` so that we can access it
|
| 81 |
+
while building the pydantic-core schema.
|
| 82 |
+
|
| 83 |
+
Attributes:
|
| 84 |
+
decorator_repr: A class variable representing the decorator string, '@root_validator'.
|
| 85 |
+
mode: The proposed validator mode.
|
| 86 |
+
"""
|
| 87 |
+
|
| 88 |
+
decorator_repr: ClassVar[str] = '@root_validator'
|
| 89 |
+
mode: Literal['before', 'after']
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
@dataclass(**slots_true)
|
| 93 |
+
class FieldSerializerDecoratorInfo:
|
| 94 |
+
"""A container for data from `@field_serializer` so that we can access it
|
| 95 |
+
while building the pydantic-core schema.
|
| 96 |
+
|
| 97 |
+
Attributes:
|
| 98 |
+
decorator_repr: A class variable representing the decorator string, '@field_serializer'.
|
| 99 |
+
fields: A tuple of field names the serializer should be called on.
|
| 100 |
+
mode: The proposed serializer mode.
|
| 101 |
+
return_type: The type of the serializer's return value.
|
| 102 |
+
when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`,
|
| 103 |
+
and `'json-unless-none'`.
|
| 104 |
+
check_fields: Whether to check that the fields actually exist on the model.
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
decorator_repr: ClassVar[str] = '@field_serializer'
|
| 108 |
+
fields: tuple[str, ...]
|
| 109 |
+
mode: Literal['plain', 'wrap']
|
| 110 |
+
return_type: Any
|
| 111 |
+
when_used: core_schema.WhenUsed
|
| 112 |
+
check_fields: bool | None
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
@dataclass(**slots_true)
|
| 116 |
+
class ModelSerializerDecoratorInfo:
|
| 117 |
+
"""A container for data from `@model_serializer` so that we can access it
|
| 118 |
+
while building the pydantic-core schema.
|
| 119 |
+
|
| 120 |
+
Attributes:
|
| 121 |
+
decorator_repr: A class variable representing the decorator string, '@model_serializer'.
|
| 122 |
+
mode: The proposed serializer mode.
|
| 123 |
+
return_type: The type of the serializer's return value.
|
| 124 |
+
when_used: The serialization condition. Accepts a string with values `'always'`, `'unless-none'`, `'json'`,
|
| 125 |
+
and `'json-unless-none'`.
|
| 126 |
+
"""
|
| 127 |
+
|
| 128 |
+
decorator_repr: ClassVar[str] = '@model_serializer'
|
| 129 |
+
mode: Literal['plain', 'wrap']
|
| 130 |
+
return_type: Any
|
| 131 |
+
when_used: core_schema.WhenUsed
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
@dataclass(**slots_true)
|
| 135 |
+
class ModelValidatorDecoratorInfo:
|
| 136 |
+
"""A container for data from `@model_validator` so that we can access it
|
| 137 |
+
while building the pydantic-core schema.
|
| 138 |
+
|
| 139 |
+
Attributes:
|
| 140 |
+
decorator_repr: A class variable representing the decorator string, '@model_validator'.
|
| 141 |
+
mode: The proposed serializer mode.
|
| 142 |
+
"""
|
| 143 |
+
|
| 144 |
+
decorator_repr: ClassVar[str] = '@model_validator'
|
| 145 |
+
mode: Literal['wrap', 'before', 'after']
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
DecoratorInfo: TypeAlias = """Union[
|
| 149 |
+
ValidatorDecoratorInfo,
|
| 150 |
+
FieldValidatorDecoratorInfo,
|
| 151 |
+
RootValidatorDecoratorInfo,
|
| 152 |
+
FieldSerializerDecoratorInfo,
|
| 153 |
+
ModelSerializerDecoratorInfo,
|
| 154 |
+
ModelValidatorDecoratorInfo,
|
| 155 |
+
ComputedFieldInfo,
|
| 156 |
+
]"""
|
| 157 |
+
|
| 158 |
+
ReturnType = TypeVar('ReturnType')
|
| 159 |
+
DecoratedType: TypeAlias = (
|
| 160 |
+
'Union[classmethod[Any, Any, ReturnType], staticmethod[Any, ReturnType], Callable[..., ReturnType], property]'
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
@dataclass # can't use slots here since we set attributes on `__post_init__`
|
| 165 |
+
class PydanticDescriptorProxy(Generic[ReturnType]):
|
| 166 |
+
"""Wrap a classmethod, staticmethod, property or unbound function
|
| 167 |
+
and act as a descriptor that allows us to detect decorated items
|
| 168 |
+
from the class' attributes.
|
| 169 |
+
|
| 170 |
+
This class' __get__ returns the wrapped item's __get__ result,
|
| 171 |
+
which makes it transparent for classmethods and staticmethods.
|
| 172 |
+
|
| 173 |
+
Attributes:
|
| 174 |
+
wrapped: The decorator that has to be wrapped.
|
| 175 |
+
decorator_info: The decorator info.
|
| 176 |
+
shim: A wrapper function to wrap V1 style function.
|
| 177 |
+
"""
|
| 178 |
+
|
| 179 |
+
wrapped: DecoratedType[ReturnType]
|
| 180 |
+
decorator_info: DecoratorInfo
|
| 181 |
+
shim: Callable[[Callable[..., Any]], Callable[..., Any]] | None = None
|
| 182 |
+
|
| 183 |
+
def __post_init__(self):
|
| 184 |
+
for attr in 'setter', 'deleter':
|
| 185 |
+
if hasattr(self.wrapped, attr):
|
| 186 |
+
f = partial(self._call_wrapped_attr, name=attr)
|
| 187 |
+
setattr(self, attr, f)
|
| 188 |
+
|
| 189 |
+
def _call_wrapped_attr(self, func: Callable[[Any], None], *, name: str) -> PydanticDescriptorProxy[ReturnType]:
|
| 190 |
+
self.wrapped = getattr(self.wrapped, name)(func)
|
| 191 |
+
if isinstance(self.wrapped, property):
|
| 192 |
+
# update ComputedFieldInfo.wrapped_property
|
| 193 |
+
from ..fields import ComputedFieldInfo
|
| 194 |
+
|
| 195 |
+
if isinstance(self.decorator_info, ComputedFieldInfo):
|
| 196 |
+
self.decorator_info.wrapped_property = self.wrapped
|
| 197 |
+
return self
|
| 198 |
+
|
| 199 |
+
def __get__(self, obj: object | None, obj_type: type[object] | None = None) -> PydanticDescriptorProxy[ReturnType]:
|
| 200 |
+
try:
|
| 201 |
+
return self.wrapped.__get__(obj, obj_type) # pyright: ignore[reportReturnType]
|
| 202 |
+
except AttributeError:
|
| 203 |
+
# not a descriptor, e.g. a partial object
|
| 204 |
+
return self.wrapped # type: ignore[return-value]
|
| 205 |
+
|
| 206 |
+
def __set_name__(self, instance: Any, name: str) -> None:
|
| 207 |
+
if hasattr(self.wrapped, '__set_name__'):
|
| 208 |
+
self.wrapped.__set_name__(instance, name) # pyright: ignore[reportFunctionMemberAccess]
|
| 209 |
+
|
| 210 |
+
def __getattr__(self, name: str, /) -> Any:
|
| 211 |
+
"""Forward checks for __isabstractmethod__ and such."""
|
| 212 |
+
return getattr(self.wrapped, name)
|
| 213 |
+
|
| 214 |
+
|
| 215 |
+
DecoratorInfoType = TypeVar('DecoratorInfoType', bound=DecoratorInfo)
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
@dataclass(**slots_true)
|
| 219 |
+
class Decorator(Generic[DecoratorInfoType]):
|
| 220 |
+
"""A generic container class to join together the decorator metadata
|
| 221 |
+
(metadata from decorator itself, which we have when the
|
| 222 |
+
decorator is called but not when we are building the core-schema)
|
| 223 |
+
and the bound function (which we have after the class itself is created).
|
| 224 |
+
|
| 225 |
+
Attributes:
|
| 226 |
+
cls_ref: The class ref.
|
| 227 |
+
cls_var_name: The decorated function name.
|
| 228 |
+
func: The decorated function.
|
| 229 |
+
shim: A wrapper function to wrap V1 style function.
|
| 230 |
+
info: The decorator info.
|
| 231 |
+
"""
|
| 232 |
+
|
| 233 |
+
cls_ref: str
|
| 234 |
+
cls_var_name: str
|
| 235 |
+
func: Callable[..., Any]
|
| 236 |
+
shim: Callable[[Any], Any] | None
|
| 237 |
+
info: DecoratorInfoType
|
| 238 |
+
|
| 239 |
+
@staticmethod
|
| 240 |
+
def build(
|
| 241 |
+
cls_: Any,
|
| 242 |
+
*,
|
| 243 |
+
cls_var_name: str,
|
| 244 |
+
shim: Callable[[Any], Any] | None,
|
| 245 |
+
info: DecoratorInfoType,
|
| 246 |
+
) -> Decorator[DecoratorInfoType]:
|
| 247 |
+
"""Build a new decorator.
|
| 248 |
+
|
| 249 |
+
Args:
|
| 250 |
+
cls_: The class.
|
| 251 |
+
cls_var_name: The decorated function name.
|
| 252 |
+
shim: A wrapper function to wrap V1 style function.
|
| 253 |
+
info: The decorator info.
|
| 254 |
+
|
| 255 |
+
Returns:
|
| 256 |
+
The new decorator instance.
|
| 257 |
+
"""
|
| 258 |
+
func = get_attribute_from_bases(cls_, cls_var_name)
|
| 259 |
+
if shim is not None:
|
| 260 |
+
func = shim(func)
|
| 261 |
+
func = unwrap_wrapped_function(func, unwrap_partial=False)
|
| 262 |
+
if not callable(func):
|
| 263 |
+
# TODO most likely this branch can be removed when we drop support for Python 3.12:
|
| 264 |
+
# This branch will get hit for classmethod properties
|
| 265 |
+
attribute = get_attribute_from_base_dicts(cls_, cls_var_name) # prevents the binding call to `__get__`
|
| 266 |
+
if isinstance(attribute, PydanticDescriptorProxy):
|
| 267 |
+
func = unwrap_wrapped_function(attribute.wrapped)
|
| 268 |
+
return Decorator(
|
| 269 |
+
cls_ref=get_type_ref(cls_),
|
| 270 |
+
cls_var_name=cls_var_name,
|
| 271 |
+
func=func,
|
| 272 |
+
shim=shim,
|
| 273 |
+
info=info,
|
| 274 |
+
)
|
| 275 |
+
|
| 276 |
+
def bind_to_cls(self, cls: Any) -> Decorator[DecoratorInfoType]:
|
| 277 |
+
"""Bind the decorator to a class.
|
| 278 |
+
|
| 279 |
+
Args:
|
| 280 |
+
cls: the class.
|
| 281 |
+
|
| 282 |
+
Returns:
|
| 283 |
+
The new decorator instance.
|
| 284 |
+
"""
|
| 285 |
+
return self.build(
|
| 286 |
+
cls,
|
| 287 |
+
cls_var_name=self.cls_var_name,
|
| 288 |
+
shim=self.shim,
|
| 289 |
+
info=copy(self.info),
|
| 290 |
+
)
|
| 291 |
+
|
| 292 |
+
|
| 293 |
+
def get_bases(tp: type[Any]) -> tuple[type[Any], ...]:
|
| 294 |
+
"""Get the base classes of a class or typeddict.
|
| 295 |
+
|
| 296 |
+
Args:
|
| 297 |
+
tp: The type or class to get the bases.
|
| 298 |
+
|
| 299 |
+
Returns:
|
| 300 |
+
The base classes.
|
| 301 |
+
"""
|
| 302 |
+
if is_typeddict(tp):
|
| 303 |
+
return tp.__orig_bases__ # type: ignore
|
| 304 |
+
try:
|
| 305 |
+
return tp.__bases__
|
| 306 |
+
except AttributeError:
|
| 307 |
+
return ()
|
| 308 |
+
|
| 309 |
+
|
| 310 |
+
def mro(tp: type[Any]) -> tuple[type[Any], ...]:
|
| 311 |
+
"""Calculate the Method Resolution Order of bases using the C3 algorithm.
|
| 312 |
+
|
| 313 |
+
See https://www.python.org/download/releases/2.3/mro/
|
| 314 |
+
"""
|
| 315 |
+
# try to use the existing mro, for performance mainly
|
| 316 |
+
# but also because it helps verify the implementation below
|
| 317 |
+
if not is_typeddict(tp):
|
| 318 |
+
try:
|
| 319 |
+
return tp.__mro__
|
| 320 |
+
except AttributeError:
|
| 321 |
+
# GenericAlias and some other cases
|
| 322 |
+
pass
|
| 323 |
+
|
| 324 |
+
bases = get_bases(tp)
|
| 325 |
+
return (tp,) + mro_for_bases(bases)
|
| 326 |
+
|
| 327 |
+
|
| 328 |
+
def mro_for_bases(bases: tuple[type[Any], ...]) -> tuple[type[Any], ...]:
|
| 329 |
+
def merge_seqs(seqs: list[deque[type[Any]]]) -> Iterable[type[Any]]:
|
| 330 |
+
while True:
|
| 331 |
+
non_empty = [seq for seq in seqs if seq]
|
| 332 |
+
if not non_empty:
|
| 333 |
+
# Nothing left to process, we're done.
|
| 334 |
+
return
|
| 335 |
+
candidate: type[Any] | None = None
|
| 336 |
+
for seq in non_empty: # Find merge candidates among seq heads.
|
| 337 |
+
candidate = seq[0]
|
| 338 |
+
not_head = [s for s in non_empty if candidate in islice(s, 1, None)]
|
| 339 |
+
if not_head:
|
| 340 |
+
# Reject the candidate.
|
| 341 |
+
candidate = None
|
| 342 |
+
else:
|
| 343 |
+
break
|
| 344 |
+
if not candidate:
|
| 345 |
+
raise TypeError('Inconsistent hierarchy, no C3 MRO is possible')
|
| 346 |
+
yield candidate
|
| 347 |
+
for seq in non_empty:
|
| 348 |
+
# Remove candidate.
|
| 349 |
+
if seq[0] == candidate:
|
| 350 |
+
seq.popleft()
|
| 351 |
+
|
| 352 |
+
seqs = [deque(mro(base)) for base in bases] + [deque(bases)]
|
| 353 |
+
return tuple(merge_seqs(seqs))
|
| 354 |
+
|
| 355 |
+
|
| 356 |
+
_sentinel = object()
|
| 357 |
+
|
| 358 |
+
|
| 359 |
+
def get_attribute_from_bases(tp: type[Any] | tuple[type[Any], ...], name: str) -> Any:
|
| 360 |
+
"""Get the attribute from the next class in the MRO that has it,
|
| 361 |
+
aiming to simulate calling the method on the actual class.
|
| 362 |
+
|
| 363 |
+
The reason for iterating over the mro instead of just getting
|
| 364 |
+
the attribute (which would do that for us) is to support TypedDict,
|
| 365 |
+
which lacks a real __mro__, but can have a virtual one constructed
|
| 366 |
+
from its bases (as done here).
|
| 367 |
+
|
| 368 |
+
Args:
|
| 369 |
+
tp: The type or class to search for the attribute. If a tuple, this is treated as a set of base classes.
|
| 370 |
+
name: The name of the attribute to retrieve.
|
| 371 |
+
|
| 372 |
+
Returns:
|
| 373 |
+
Any: The attribute value, if found.
|
| 374 |
+
|
| 375 |
+
Raises:
|
| 376 |
+
AttributeError: If the attribute is not found in any class in the MRO.
|
| 377 |
+
"""
|
| 378 |
+
if isinstance(tp, tuple):
|
| 379 |
+
for base in mro_for_bases(tp):
|
| 380 |
+
attribute = base.__dict__.get(name, _sentinel)
|
| 381 |
+
if attribute is not _sentinel:
|
| 382 |
+
attribute_get = getattr(attribute, '__get__', None)
|
| 383 |
+
if attribute_get is not None:
|
| 384 |
+
return attribute_get(None, tp)
|
| 385 |
+
return attribute
|
| 386 |
+
raise AttributeError(f'{name} not found in {tp}')
|
| 387 |
+
else:
|
| 388 |
+
try:
|
| 389 |
+
return getattr(tp, name)
|
| 390 |
+
except AttributeError:
|
| 391 |
+
return get_attribute_from_bases(mro(tp), name)
|
| 392 |
+
|
| 393 |
+
|
| 394 |
+
def get_attribute_from_base_dicts(tp: type[Any], name: str) -> Any:
|
| 395 |
+
"""Get an attribute out of the `__dict__` following the MRO.
|
| 396 |
+
This prevents the call to `__get__` on the descriptor, and allows
|
| 397 |
+
us to get the original function for classmethod properties.
|
| 398 |
+
|
| 399 |
+
Args:
|
| 400 |
+
tp: The type or class to search for the attribute.
|
| 401 |
+
name: The name of the attribute to retrieve.
|
| 402 |
+
|
| 403 |
+
Returns:
|
| 404 |
+
Any: The attribute value, if found.
|
| 405 |
+
|
| 406 |
+
Raises:
|
| 407 |
+
KeyError: If the attribute is not found in any class's `__dict__` in the MRO.
|
| 408 |
+
"""
|
| 409 |
+
for base in reversed(mro(tp)):
|
| 410 |
+
if name in base.__dict__:
|
| 411 |
+
return base.__dict__[name]
|
| 412 |
+
return tp.__dict__[name] # raise the error
|
| 413 |
+
|
| 414 |
+
|
| 415 |
+
@dataclass(**slots_true)
|
| 416 |
+
class DecoratorInfos:
|
| 417 |
+
"""Mapping of name in the class namespace to decorator info.
|
| 418 |
+
|
| 419 |
+
note that the name in the class namespace is the function or attribute name
|
| 420 |
+
not the field name!
|
| 421 |
+
"""
|
| 422 |
+
|
| 423 |
+
validators: dict[str, Decorator[ValidatorDecoratorInfo]] = field(default_factory=dict)
|
| 424 |
+
field_validators: dict[str, Decorator[FieldValidatorDecoratorInfo]] = field(default_factory=dict)
|
| 425 |
+
root_validators: dict[str, Decorator[RootValidatorDecoratorInfo]] = field(default_factory=dict)
|
| 426 |
+
field_serializers: dict[str, Decorator[FieldSerializerDecoratorInfo]] = field(default_factory=dict)
|
| 427 |
+
model_serializers: dict[str, Decorator[ModelSerializerDecoratorInfo]] = field(default_factory=dict)
|
| 428 |
+
model_validators: dict[str, Decorator[ModelValidatorDecoratorInfo]] = field(default_factory=dict)
|
| 429 |
+
computed_fields: dict[str, Decorator[ComputedFieldInfo]] = field(default_factory=dict)
|
| 430 |
+
|
| 431 |
+
@classmethod
|
| 432 |
+
def build(
|
| 433 |
+
cls,
|
| 434 |
+
typ: type[Any],
|
| 435 |
+
# Default to `True` for backwards compatibility:
|
| 436 |
+
replace_wrapped_methods: bool = True,
|
| 437 |
+
) -> Self:
|
| 438 |
+
"""Build a `DecoratorInfos` instance for the given model, dataclass or `TypedDict` type.
|
| 439 |
+
|
| 440 |
+
Decorators from parent classes are included, including "bare" classes (e.g. if `typ`
|
| 441 |
+
is a Pydantic model, non Pydantic parent model classes are also taken into account).
|
| 442 |
+
The collection of the decorators happens by respecting the MRO.
|
| 443 |
+
|
| 444 |
+
If one of the bases has an `__pydantic_decorators__` attribute set, it is assumed to be
|
| 445 |
+
a `DecoratorInfos` instance and is used as-is. The `__pydantic_decorators__` attribute
|
| 446 |
+
is *not* being set on the provided `typ`.
|
| 447 |
+
|
| 448 |
+
Args:
|
| 449 |
+
typ: The model, dataclass or `TypedDict` type to use when building the `DecoratorInfos` instance.
|
| 450 |
+
replace_wrapped_methods: Whether to replace the decorator's wrapped methods on `typ`.
|
| 451 |
+
This is useful e.g. for field validators which are initially class methods. This should
|
| 452 |
+
only be set to `True` if `typ` is a Pydantic model or dataclass (otherwise this results
|
| 453 |
+
in mutations of classes Pydantic doesn't "own").
|
| 454 |
+
"""
|
| 455 |
+
# reminder: dicts are ordered and replacement does not alter the order
|
| 456 |
+
res = cls()
|
| 457 |
+
# Iterate over the bases, without the actual `typ`.
|
| 458 |
+
# `1:-1` because we don't need to include `object`/`TypedDict`:
|
| 459 |
+
for base in reversed(mro(typ)[1:-1]):
|
| 460 |
+
existing: DecoratorInfos | None = base.__dict__.get('__pydantic_decorators__')
|
| 461 |
+
if existing is None:
|
| 462 |
+
existing, _ = _decorator_infos_for_class(base, collect_to_replace=False)
|
| 463 |
+
res.validators.update({k: v.bind_to_cls(typ) for k, v in existing.validators.items()})
|
| 464 |
+
res.field_validators.update({k: v.bind_to_cls(typ) for k, v in existing.field_validators.items()})
|
| 465 |
+
res.root_validators.update({k: v.bind_to_cls(typ) for k, v in existing.root_validators.items()})
|
| 466 |
+
res.field_serializers.update({k: v.bind_to_cls(typ) for k, v in existing.field_serializers.items()})
|
| 467 |
+
res.model_serializers.update({k: v.bind_to_cls(typ) for k, v in existing.model_serializers.items()})
|
| 468 |
+
res.model_validators.update({k: v.bind_to_cls(typ) for k, v in existing.model_validators.items()})
|
| 469 |
+
res.computed_fields.update({k: v.bind_to_cls(typ) for k, v in existing.computed_fields.items()})
|
| 470 |
+
|
| 471 |
+
decorator_infos, to_replace = _decorator_infos_for_class(typ, collect_to_replace=True)
|
| 472 |
+
|
| 473 |
+
res.validators.update(decorator_infos.validators)
|
| 474 |
+
res.field_validators.update(decorator_infos.field_validators)
|
| 475 |
+
res.root_validators.update(decorator_infos.root_validators)
|
| 476 |
+
res.field_serializers.update(decorator_infos.field_serializers)
|
| 477 |
+
res.model_serializers.update(decorator_infos.model_serializers)
|
| 478 |
+
res.model_validators.update(decorator_infos.model_validators)
|
| 479 |
+
res.computed_fields.update(decorator_infos.computed_fields)
|
| 480 |
+
|
| 481 |
+
if replace_wrapped_methods and to_replace:
|
| 482 |
+
for name, value in to_replace:
|
| 483 |
+
setattr(typ, name, value)
|
| 484 |
+
|
| 485 |
+
res._validate()
|
| 486 |
+
return res
|
| 487 |
+
|
| 488 |
+
def _validate(self) -> None:
|
| 489 |
+
seen: set[str] = set()
|
| 490 |
+
for field_ser in self.field_serializers.values():
|
| 491 |
+
for f_name in field_ser.info.fields:
|
| 492 |
+
if f_name in seen:
|
| 493 |
+
raise PydanticUserError(
|
| 494 |
+
f'Multiple field serializer functions were defined for field {f_name!r}, this is not allowed.',
|
| 495 |
+
code='multiple-field-serializers',
|
| 496 |
+
)
|
| 497 |
+
seen.add(f_name)
|
| 498 |
+
|
| 499 |
+
def update_from_config(self, config_wrapper: ConfigWrapper) -> None:
|
| 500 |
+
"""Update the decorator infos from the configuration of the class they are attached to."""
|
| 501 |
+
for name, computed_field_dec in self.computed_fields.items():
|
| 502 |
+
computed_field_dec.info._update_from_config(config_wrapper, name)
|
| 503 |
+
|
| 504 |
+
|
| 505 |
+
def _decorator_infos_for_class(
|
| 506 |
+
typ: type[Any],
|
| 507 |
+
*,
|
| 508 |
+
collect_to_replace: bool,
|
| 509 |
+
) -> tuple[DecoratorInfos, list[tuple[str, Any]]]:
|
| 510 |
+
"""Collect a `DecoratorInfos` for class, without looking into bases."""
|
| 511 |
+
res = DecoratorInfos()
|
| 512 |
+
to_replace: list[tuple[str, Any]] = []
|
| 513 |
+
|
| 514 |
+
for var_name, var_value in vars(typ).items():
|
| 515 |
+
if isinstance(var_value, PydanticDescriptorProxy):
|
| 516 |
+
info = var_value.decorator_info
|
| 517 |
+
if isinstance(info, ValidatorDecoratorInfo):
|
| 518 |
+
res.validators[var_name] = Decorator.build(typ, cls_var_name=var_name, shim=var_value.shim, info=info)
|
| 519 |
+
elif isinstance(info, FieldValidatorDecoratorInfo):
|
| 520 |
+
res.field_validators[var_name] = Decorator.build(
|
| 521 |
+
typ, cls_var_name=var_name, shim=var_value.shim, info=info
|
| 522 |
+
)
|
| 523 |
+
elif isinstance(info, RootValidatorDecoratorInfo):
|
| 524 |
+
res.root_validators[var_name] = Decorator.build(
|
| 525 |
+
typ, cls_var_name=var_name, shim=var_value.shim, info=info
|
| 526 |
+
)
|
| 527 |
+
elif isinstance(info, FieldSerializerDecoratorInfo):
|
| 528 |
+
res.field_serializers[var_name] = Decorator.build(
|
| 529 |
+
typ, cls_var_name=var_name, shim=var_value.shim, info=info
|
| 530 |
+
)
|
| 531 |
+
elif isinstance(info, ModelValidatorDecoratorInfo):
|
| 532 |
+
res.model_validators[var_name] = Decorator.build(
|
| 533 |
+
typ, cls_var_name=var_name, shim=var_value.shim, info=info
|
| 534 |
+
)
|
| 535 |
+
elif isinstance(info, ModelSerializerDecoratorInfo):
|
| 536 |
+
res.model_serializers[var_name] = Decorator.build(
|
| 537 |
+
typ, cls_var_name=var_name, shim=var_value.shim, info=info
|
| 538 |
+
)
|
| 539 |
+
else:
|
| 540 |
+
from ..fields import ComputedFieldInfo
|
| 541 |
+
|
| 542 |
+
isinstance(var_value, ComputedFieldInfo)
|
| 543 |
+
res.computed_fields[var_name] = Decorator.build(typ, cls_var_name=var_name, shim=None, info=info)
|
| 544 |
+
if collect_to_replace:
|
| 545 |
+
to_replace.append((var_name, var_value.wrapped))
|
| 546 |
+
|
| 547 |
+
return res, to_replace
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def inspect_validator(
|
| 551 |
+
validator: Callable[..., Any], *, mode: FieldValidatorModes, type: Literal['field', 'model']
|
| 552 |
+
) -> bool:
|
| 553 |
+
"""Look at a field or model validator function and determine whether it takes an info argument.
|
| 554 |
+
|
| 555 |
+
An error is raised if the function has an invalid signature.
|
| 556 |
+
|
| 557 |
+
Args:
|
| 558 |
+
validator: The validator function to inspect.
|
| 559 |
+
mode: The proposed validator mode.
|
| 560 |
+
type: The type of validator, either 'field' or 'model'.
|
| 561 |
+
|
| 562 |
+
Returns:
|
| 563 |
+
Whether the validator takes an info argument.
|
| 564 |
+
"""
|
| 565 |
+
try:
|
| 566 |
+
sig = signature_no_eval(validator)
|
| 567 |
+
except (ValueError, TypeError):
|
| 568 |
+
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
|
| 569 |
+
# In this case, we assume no info argument is present:
|
| 570 |
+
return False
|
| 571 |
+
n_positional = count_positional_required_params(sig)
|
| 572 |
+
if mode == 'wrap':
|
| 573 |
+
if n_positional == 3:
|
| 574 |
+
return True
|
| 575 |
+
elif n_positional == 2:
|
| 576 |
+
return False
|
| 577 |
+
else:
|
| 578 |
+
assert mode in {'before', 'after', 'plain'}, f"invalid mode: {mode!r}, expected 'before', 'after' or 'plain"
|
| 579 |
+
if n_positional == 2:
|
| 580 |
+
return True
|
| 581 |
+
elif n_positional == 1:
|
| 582 |
+
return False
|
| 583 |
+
|
| 584 |
+
raise PydanticUserError(
|
| 585 |
+
f'Unrecognized {type} validator function signature for {validator} with `mode={mode}`: {sig}',
|
| 586 |
+
code='validator-signature',
|
| 587 |
+
)
|
| 588 |
+
|
| 589 |
+
|
| 590 |
+
def inspect_field_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> tuple[bool, bool]:
|
| 591 |
+
"""Look at a field serializer function and determine if it is a field serializer,
|
| 592 |
+
and whether it takes an info argument.
|
| 593 |
+
|
| 594 |
+
An error is raised if the function has an invalid signature.
|
| 595 |
+
|
| 596 |
+
Args:
|
| 597 |
+
serializer: The serializer function to inspect.
|
| 598 |
+
mode: The serializer mode, either 'plain' or 'wrap'.
|
| 599 |
+
|
| 600 |
+
Returns:
|
| 601 |
+
Tuple of (is_field_serializer, info_arg).
|
| 602 |
+
"""
|
| 603 |
+
try:
|
| 604 |
+
sig = signature_no_eval(serializer)
|
| 605 |
+
except (ValueError, TypeError):
|
| 606 |
+
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
|
| 607 |
+
# In this case, we assume no info argument is present and this is not a method:
|
| 608 |
+
return (False, False)
|
| 609 |
+
|
| 610 |
+
first = next(iter(sig.parameters.values()), None)
|
| 611 |
+
is_field_serializer = first is not None and first.name == 'self'
|
| 612 |
+
|
| 613 |
+
n_positional = count_positional_required_params(sig)
|
| 614 |
+
if is_field_serializer:
|
| 615 |
+
# -1 to correct for self parameter
|
| 616 |
+
info_arg = _serializer_info_arg(mode, n_positional - 1)
|
| 617 |
+
else:
|
| 618 |
+
info_arg = _serializer_info_arg(mode, n_positional)
|
| 619 |
+
|
| 620 |
+
if info_arg is None:
|
| 621 |
+
raise PydanticUserError(
|
| 622 |
+
f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}',
|
| 623 |
+
code='field-serializer-signature',
|
| 624 |
+
)
|
| 625 |
+
|
| 626 |
+
return is_field_serializer, info_arg
|
| 627 |
+
|
| 628 |
+
|
| 629 |
+
def inspect_annotated_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool:
|
| 630 |
+
"""Look at a serializer function used via `Annotated` and determine whether it takes an info argument.
|
| 631 |
+
|
| 632 |
+
An error is raised if the function has an invalid signature.
|
| 633 |
+
|
| 634 |
+
Args:
|
| 635 |
+
serializer: The serializer function to check.
|
| 636 |
+
mode: The serializer mode, either 'plain' or 'wrap'.
|
| 637 |
+
|
| 638 |
+
Returns:
|
| 639 |
+
info_arg
|
| 640 |
+
"""
|
| 641 |
+
try:
|
| 642 |
+
sig = signature_no_eval(serializer)
|
| 643 |
+
except (ValueError, TypeError):
|
| 644 |
+
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
|
| 645 |
+
# In this case, we assume no info argument is present:
|
| 646 |
+
return False
|
| 647 |
+
info_arg = _serializer_info_arg(mode, count_positional_required_params(sig))
|
| 648 |
+
if info_arg is None:
|
| 649 |
+
raise PydanticUserError(
|
| 650 |
+
f'Unrecognized field_serializer function signature for {serializer} with `mode={mode}`:{sig}',
|
| 651 |
+
code='field-serializer-signature',
|
| 652 |
+
)
|
| 653 |
+
else:
|
| 654 |
+
return info_arg
|
| 655 |
+
|
| 656 |
+
|
| 657 |
+
def inspect_model_serializer(serializer: Callable[..., Any], mode: Literal['plain', 'wrap']) -> bool:
|
| 658 |
+
"""Look at a model serializer function and determine whether it takes an info argument.
|
| 659 |
+
|
| 660 |
+
An error is raised if the function has an invalid signature.
|
| 661 |
+
|
| 662 |
+
Args:
|
| 663 |
+
serializer: The serializer function to check.
|
| 664 |
+
mode: The serializer mode, either 'plain' or 'wrap'.
|
| 665 |
+
|
| 666 |
+
Returns:
|
| 667 |
+
`info_arg` - whether the function expects an info argument.
|
| 668 |
+
"""
|
| 669 |
+
if isinstance(serializer, (staticmethod, classmethod)) or not is_instance_method_from_sig(serializer):
|
| 670 |
+
raise PydanticUserError(
|
| 671 |
+
'`@model_serializer` must be applied to instance methods', code='model-serializer-instance-method'
|
| 672 |
+
)
|
| 673 |
+
|
| 674 |
+
sig = signature_no_eval(serializer)
|
| 675 |
+
info_arg = _serializer_info_arg(mode, count_positional_required_params(sig))
|
| 676 |
+
if info_arg is None:
|
| 677 |
+
raise PydanticUserError(
|
| 678 |
+
f'Unrecognized model_serializer function signature for {serializer} with `mode={mode}`:{sig}',
|
| 679 |
+
code='model-serializer-signature',
|
| 680 |
+
)
|
| 681 |
+
else:
|
| 682 |
+
return info_arg
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
def _serializer_info_arg(mode: Literal['plain', 'wrap'], n_positional: int) -> bool | None:
|
| 686 |
+
if mode == 'plain':
|
| 687 |
+
if n_positional == 1:
|
| 688 |
+
# (input_value: Any, /) -> Any
|
| 689 |
+
return False
|
| 690 |
+
elif n_positional == 2:
|
| 691 |
+
# (model: Any, input_value: Any, /) -> Any
|
| 692 |
+
return True
|
| 693 |
+
else:
|
| 694 |
+
assert mode == 'wrap', f"invalid mode: {mode!r}, expected 'plain' or 'wrap'"
|
| 695 |
+
if n_positional == 2:
|
| 696 |
+
# (input_value: Any, serializer: SerializerFunctionWrapHandler, /) -> Any
|
| 697 |
+
return False
|
| 698 |
+
elif n_positional == 3:
|
| 699 |
+
# (input_value: Any, serializer: SerializerFunctionWrapHandler, info: SerializationInfo, /) -> Any
|
| 700 |
+
return True
|
| 701 |
+
|
| 702 |
+
return None
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
AnyDecoratorCallable: TypeAlias = (
|
| 706 |
+
'Union[classmethod[Any, Any, Any], staticmethod[Any, Any], partialmethod[Any], Callable[..., Any]]'
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
|
| 710 |
+
def is_instance_method_from_sig(function: AnyDecoratorCallable) -> bool:
|
| 711 |
+
"""Whether the function is an instance method.
|
| 712 |
+
|
| 713 |
+
It will consider a function as instance method if the first parameter of
|
| 714 |
+
function is `self`.
|
| 715 |
+
|
| 716 |
+
Args:
|
| 717 |
+
function: The function to check.
|
| 718 |
+
|
| 719 |
+
Returns:
|
| 720 |
+
`True` if the function is an instance method, `False` otherwise.
|
| 721 |
+
"""
|
| 722 |
+
sig = signature_no_eval(unwrap_wrapped_function(function))
|
| 723 |
+
first = next(iter(sig.parameters.values()), None)
|
| 724 |
+
if first and first.name == 'self':
|
| 725 |
+
return True
|
| 726 |
+
return False
|
| 727 |
+
|
| 728 |
+
|
| 729 |
+
def ensure_classmethod_based_on_signature(function: AnyDecoratorCallable) -> Any:
|
| 730 |
+
"""Apply the `@classmethod` decorator on the function.
|
| 731 |
+
|
| 732 |
+
Args:
|
| 733 |
+
function: The function to apply the decorator on.
|
| 734 |
+
|
| 735 |
+
Return:
|
| 736 |
+
The `@classmethod` decorator applied function.
|
| 737 |
+
"""
|
| 738 |
+
if not isinstance(
|
| 739 |
+
unwrap_wrapped_function(function, unwrap_class_static_method=False), classmethod
|
| 740 |
+
) and _is_classmethod_from_sig(function):
|
| 741 |
+
return classmethod(function) # type: ignore[arg-type]
|
| 742 |
+
return function
|
| 743 |
+
|
| 744 |
+
|
| 745 |
+
def _is_classmethod_from_sig(function: AnyDecoratorCallable) -> bool:
|
| 746 |
+
sig = signature_no_eval(unwrap_wrapped_function(function))
|
| 747 |
+
first = next(iter(sig.parameters.values()), None)
|
| 748 |
+
if first and first.name == 'cls':
|
| 749 |
+
return True
|
| 750 |
+
return False
|
| 751 |
+
|
| 752 |
+
|
| 753 |
+
def unwrap_wrapped_function(
|
| 754 |
+
func: Any,
|
| 755 |
+
*,
|
| 756 |
+
unwrap_partial: bool = True,
|
| 757 |
+
unwrap_class_static_method: bool = True,
|
| 758 |
+
) -> Any:
|
| 759 |
+
"""Recursively unwraps a wrapped function until the underlying function is reached.
|
| 760 |
+
This handles property, functools.partial, functools.partialmethod, staticmethod, and classmethod.
|
| 761 |
+
|
| 762 |
+
Args:
|
| 763 |
+
func: The function to unwrap.
|
| 764 |
+
unwrap_partial: If True (default), unwrap partial and partialmethod decorators.
|
| 765 |
+
unwrap_class_static_method: If True (default), also unwrap classmethod and staticmethod
|
| 766 |
+
decorators. If False, only unwrap partial and partialmethod decorators.
|
| 767 |
+
|
| 768 |
+
Returns:
|
| 769 |
+
The underlying function of the wrapped function.
|
| 770 |
+
"""
|
| 771 |
+
# Define the types we want to check against as a single tuple.
|
| 772 |
+
unwrap_types = (
|
| 773 |
+
(property, cached_property)
|
| 774 |
+
+ ((partial, partialmethod) if unwrap_partial else ())
|
| 775 |
+
+ ((staticmethod, classmethod) if unwrap_class_static_method else ())
|
| 776 |
+
)
|
| 777 |
+
|
| 778 |
+
while isinstance(func, unwrap_types):
|
| 779 |
+
if unwrap_class_static_method and isinstance(func, (classmethod, staticmethod)):
|
| 780 |
+
func = func.__func__
|
| 781 |
+
elif isinstance(func, (partial, partialmethod)):
|
| 782 |
+
func = func.func
|
| 783 |
+
elif isinstance(func, property):
|
| 784 |
+
func = func.fget # arbitrary choice, convenient for computed fields
|
| 785 |
+
else:
|
| 786 |
+
# Make coverage happy as it can only get here in the last possible case
|
| 787 |
+
assert isinstance(func, cached_property)
|
| 788 |
+
func = func.func # type: ignore
|
| 789 |
+
|
| 790 |
+
return func
|
| 791 |
+
|
| 792 |
+
|
| 793 |
+
_function_like = (
|
| 794 |
+
partial,
|
| 795 |
+
partialmethod,
|
| 796 |
+
types.FunctionType,
|
| 797 |
+
types.BuiltinFunctionType,
|
| 798 |
+
types.MethodType,
|
| 799 |
+
types.WrapperDescriptorType,
|
| 800 |
+
types.MethodWrapperType,
|
| 801 |
+
types.MemberDescriptorType,
|
| 802 |
+
)
|
| 803 |
+
|
| 804 |
+
|
| 805 |
+
def get_callable_return_type(
|
| 806 |
+
callable_obj: Any,
|
| 807 |
+
globalns: GlobalsNamespace | None = None,
|
| 808 |
+
localns: MappingNamespace | None = None,
|
| 809 |
+
) -> Any | PydanticUndefinedType:
|
| 810 |
+
"""Get the callable return type.
|
| 811 |
+
|
| 812 |
+
Args:
|
| 813 |
+
callable_obj: The callable to analyze.
|
| 814 |
+
globalns: The globals namespace to use during type annotation evaluation.
|
| 815 |
+
localns: The locals namespace to use during type annotation evaluation.
|
| 816 |
+
|
| 817 |
+
Returns:
|
| 818 |
+
The function return type.
|
| 819 |
+
"""
|
| 820 |
+
if isinstance(callable_obj, type):
|
| 821 |
+
# types are callables, and we assume the return type
|
| 822 |
+
# is the type itself (e.g. `int()` results in an instance of `int`).
|
| 823 |
+
return callable_obj
|
| 824 |
+
|
| 825 |
+
if not isinstance(callable_obj, _function_like):
|
| 826 |
+
call_func = getattr(type(callable_obj), '__call__', None) # noqa: B004
|
| 827 |
+
if call_func is not None:
|
| 828 |
+
callable_obj = call_func
|
| 829 |
+
|
| 830 |
+
hints = get_function_type_hints(
|
| 831 |
+
unwrap_wrapped_function(callable_obj),
|
| 832 |
+
include_keys={'return'},
|
| 833 |
+
globalns=globalns,
|
| 834 |
+
localns=localns,
|
| 835 |
+
)
|
| 836 |
+
return hints.get('return', PydanticUndefined)
|
| 837 |
+
|
| 838 |
+
|
| 839 |
+
def count_positional_required_params(sig: Signature) -> int:
|
| 840 |
+
"""Get the number of positional (required) arguments of a signature.
|
| 841 |
+
|
| 842 |
+
This function should only be used to inspect signatures of validation and serialization functions.
|
| 843 |
+
The first argument (the value being serialized or validated) is counted as a required argument
|
| 844 |
+
even if a default value exists.
|
| 845 |
+
|
| 846 |
+
Returns:
|
| 847 |
+
The number of positional arguments of a signature.
|
| 848 |
+
"""
|
| 849 |
+
parameters = list(sig.parameters.values())
|
| 850 |
+
return sum(
|
| 851 |
+
1
|
| 852 |
+
for param in parameters
|
| 853 |
+
if can_be_positional(param)
|
| 854 |
+
# First argument is the value being validated/serialized, and can have a default value
|
| 855 |
+
# (e.g. `float`, which has signature `(x=0, /)`). We assume other parameters (the info arg
|
| 856 |
+
# for instance) should be required, and thus without any default value.
|
| 857 |
+
and (param.default is Parameter.empty or param is parameters[0])
|
| 858 |
+
)
|
| 859 |
+
|
| 860 |
+
|
| 861 |
+
def ensure_property(f: Any) -> Any:
|
| 862 |
+
"""Ensure that a function is a `property` or `cached_property`, or is a valid descriptor.
|
| 863 |
+
|
| 864 |
+
Args:
|
| 865 |
+
f: The function to check.
|
| 866 |
+
|
| 867 |
+
Returns:
|
| 868 |
+
The function, or a `property` or `cached_property` instance wrapping the function.
|
| 869 |
+
"""
|
| 870 |
+
if ismethoddescriptor(f) or isdatadescriptor(f):
|
| 871 |
+
return f
|
| 872 |
+
else:
|
| 873 |
+
return property(f)
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_decorators_v1.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Logic for V1 validators, e.g. `@validator` and `@root_validator`."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations as _annotations
|
| 4 |
+
|
| 5 |
+
from inspect import Parameter, signature
|
| 6 |
+
from typing import Any, Union, cast
|
| 7 |
+
|
| 8 |
+
from pydantic_core import core_schema
|
| 9 |
+
from typing_extensions import Protocol
|
| 10 |
+
|
| 11 |
+
from ..errors import PydanticUserError
|
| 12 |
+
from ._utils import can_be_positional
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class V1OnlyValueValidator(Protocol):
|
| 16 |
+
"""A simple validator, supported for V1 validators and V2 validators."""
|
| 17 |
+
|
| 18 |
+
def __call__(self, __value: Any) -> Any: ...
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class V1ValidatorWithValues(Protocol):
|
| 22 |
+
"""A validator with `values` argument, supported for V1 validators and V2 validators."""
|
| 23 |
+
|
| 24 |
+
def __call__(self, __value: Any, values: dict[str, Any]) -> Any: ...
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class V1ValidatorWithValuesKwOnly(Protocol):
|
| 28 |
+
"""A validator with keyword only `values` argument, supported for V1 validators and V2 validators."""
|
| 29 |
+
|
| 30 |
+
def __call__(self, __value: Any, *, values: dict[str, Any]) -> Any: ...
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class V1ValidatorWithKwargs(Protocol):
|
| 34 |
+
"""A validator with `kwargs` argument, supported for V1 validators and V2 validators."""
|
| 35 |
+
|
| 36 |
+
def __call__(self, __value: Any, **kwargs: Any) -> Any: ...
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class V1ValidatorWithValuesAndKwargs(Protocol):
|
| 40 |
+
"""A validator with `values` and `kwargs` arguments, supported for V1 validators and V2 validators."""
|
| 41 |
+
|
| 42 |
+
def __call__(self, __value: Any, values: dict[str, Any], **kwargs: Any) -> Any: ...
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
V1Validator = Union[
|
| 46 |
+
V1ValidatorWithValues, V1ValidatorWithValuesKwOnly, V1ValidatorWithKwargs, V1ValidatorWithValuesAndKwargs
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def can_be_keyword(param: Parameter) -> bool:
|
| 51 |
+
return param.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def make_generic_v1_field_validator(validator: V1Validator) -> core_schema.WithInfoValidatorFunction:
|
| 55 |
+
"""Wrap a V1 style field validator for V2 compatibility.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
validator: The V1 style field validator.
|
| 59 |
+
|
| 60 |
+
Returns:
|
| 61 |
+
A wrapped V2 style field validator.
|
| 62 |
+
|
| 63 |
+
Raises:
|
| 64 |
+
PydanticUserError: If the signature is not supported or the parameters are
|
| 65 |
+
not available in Pydantic V2.
|
| 66 |
+
"""
|
| 67 |
+
sig = signature(validator)
|
| 68 |
+
|
| 69 |
+
needs_values_kw = False
|
| 70 |
+
|
| 71 |
+
for param_num, (param_name, parameter) in enumerate(sig.parameters.items()):
|
| 72 |
+
if can_be_keyword(parameter) and param_name in ('field', 'config'):
|
| 73 |
+
raise PydanticUserError(
|
| 74 |
+
'The `field` and `config` parameters are not available in Pydantic V2, '
|
| 75 |
+
'please use the `info` parameter instead.',
|
| 76 |
+
code='validator-field-config-info',
|
| 77 |
+
)
|
| 78 |
+
if parameter.kind is Parameter.VAR_KEYWORD:
|
| 79 |
+
needs_values_kw = True
|
| 80 |
+
elif can_be_keyword(parameter) and param_name == 'values':
|
| 81 |
+
needs_values_kw = True
|
| 82 |
+
elif can_be_positional(parameter) and param_num == 0:
|
| 83 |
+
# value
|
| 84 |
+
continue
|
| 85 |
+
elif parameter.default is Parameter.empty: # ignore params with defaults e.g. bound by functools.partial
|
| 86 |
+
raise PydanticUserError(
|
| 87 |
+
f'Unsupported signature for V1 style validator {validator}: {sig} is not supported.',
|
| 88 |
+
code='validator-v1-signature',
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
if needs_values_kw:
|
| 92 |
+
# (v, **kwargs), (v, values, **kwargs), (v, *, values, **kwargs) or (v, *, values)
|
| 93 |
+
val1 = cast(V1ValidatorWithValues, validator)
|
| 94 |
+
|
| 95 |
+
def wrapper1(value: Any, info: core_schema.ValidationInfo) -> Any:
|
| 96 |
+
return val1(value, values=info.data)
|
| 97 |
+
|
| 98 |
+
return wrapper1
|
| 99 |
+
else:
|
| 100 |
+
val2 = cast(V1OnlyValueValidator, validator)
|
| 101 |
+
|
| 102 |
+
def wrapper2(value: Any, _: core_schema.ValidationInfo) -> Any:
|
| 103 |
+
return val2(value)
|
| 104 |
+
|
| 105 |
+
return wrapper2
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
RootValidatorValues = dict[str, Any]
|
| 109 |
+
# technically tuple[model_dict, model_extra, fields_set] | tuple[dataclass_dict, init_vars]
|
| 110 |
+
RootValidatorFieldsTuple = tuple[Any, ...]
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
class V1RootValidatorFunction(Protocol):
|
| 114 |
+
"""A simple root validator, supported for V1 validators and V2 validators."""
|
| 115 |
+
|
| 116 |
+
def __call__(self, __values: RootValidatorValues) -> RootValidatorValues: ...
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
class V2CoreBeforeRootValidator(Protocol):
|
| 120 |
+
"""V2 validator with mode='before'."""
|
| 121 |
+
|
| 122 |
+
def __call__(self, __values: RootValidatorValues, __info: core_schema.ValidationInfo) -> RootValidatorValues: ...
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
class V2CoreAfterRootValidator(Protocol):
|
| 126 |
+
"""V2 validator with mode='after'."""
|
| 127 |
+
|
| 128 |
+
def __call__(
|
| 129 |
+
self, __fields_tuple: RootValidatorFieldsTuple, __info: core_schema.ValidationInfo
|
| 130 |
+
) -> RootValidatorFieldsTuple: ...
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def make_v1_generic_root_validator(
|
| 134 |
+
validator: V1RootValidatorFunction, pre: bool
|
| 135 |
+
) -> V2CoreBeforeRootValidator | V2CoreAfterRootValidator:
|
| 136 |
+
"""Wrap a V1 style root validator for V2 compatibility.
|
| 137 |
+
|
| 138 |
+
Args:
|
| 139 |
+
validator: The V1 style field validator.
|
| 140 |
+
pre: Whether the validator is a pre validator.
|
| 141 |
+
|
| 142 |
+
Returns:
|
| 143 |
+
A wrapped V2 style validator.
|
| 144 |
+
"""
|
| 145 |
+
if pre is True:
|
| 146 |
+
# mode='before' for pydantic-core
|
| 147 |
+
def _wrapper1(values: RootValidatorValues, _: core_schema.ValidationInfo) -> RootValidatorValues:
|
| 148 |
+
return validator(values)
|
| 149 |
+
|
| 150 |
+
return _wrapper1
|
| 151 |
+
|
| 152 |
+
# mode='after' for pydantic-core
|
| 153 |
+
def _wrapper2(fields_tuple: RootValidatorFieldsTuple, _: core_schema.ValidationInfo) -> RootValidatorFieldsTuple:
|
| 154 |
+
if len(fields_tuple) == 2:
|
| 155 |
+
# dataclass, this is easy
|
| 156 |
+
values, init_vars = fields_tuple
|
| 157 |
+
values = validator(values)
|
| 158 |
+
return values, init_vars
|
| 159 |
+
else:
|
| 160 |
+
# ugly hack: to match v1 behaviour, we merge values and model_extra, then split them up based on fields
|
| 161 |
+
# afterwards
|
| 162 |
+
model_dict, model_extra, fields_set = fields_tuple
|
| 163 |
+
if model_extra:
|
| 164 |
+
fields = set(model_dict.keys())
|
| 165 |
+
model_dict.update(model_extra)
|
| 166 |
+
model_dict_new = validator(model_dict)
|
| 167 |
+
for k in list(model_dict_new.keys()):
|
| 168 |
+
if k not in fields:
|
| 169 |
+
model_extra[k] = model_dict_new.pop(k)
|
| 170 |
+
else:
|
| 171 |
+
model_dict_new = validator(model_dict)
|
| 172 |
+
return model_dict_new, model_extra, fields_set
|
| 173 |
+
|
| 174 |
+
return _wrapper2
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_discriminated_union.py
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations as _annotations
|
| 2 |
+
|
| 3 |
+
from collections.abc import Hashable, Sequence
|
| 4 |
+
from typing import TYPE_CHECKING, Any, cast
|
| 5 |
+
|
| 6 |
+
from pydantic_core import CoreSchema, core_schema
|
| 7 |
+
|
| 8 |
+
from ..errors import PydanticUserError
|
| 9 |
+
from . import _core_utils
|
| 10 |
+
from ._core_utils import (
|
| 11 |
+
CoreSchemaField,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
if TYPE_CHECKING:
|
| 15 |
+
from ..types import Discriminator
|
| 16 |
+
from ._core_metadata import CoreMetadata
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class MissingDefinitionForUnionRef(Exception):
|
| 20 |
+
"""Raised when applying a discriminated union discriminator to a schema
|
| 21 |
+
requires a definition that is not yet defined
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
def __init__(self, ref: str) -> None:
|
| 25 |
+
self.ref = ref
|
| 26 |
+
super().__init__(f'Missing definition for ref {self.ref!r}')
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def set_discriminator_in_metadata(schema: CoreSchema, discriminator: Any) -> None:
|
| 30 |
+
metadata = cast('CoreMetadata', schema.setdefault('metadata', {}))
|
| 31 |
+
metadata['pydantic_internal_union_discriminator'] = discriminator
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def apply_discriminator(
|
| 35 |
+
schema: core_schema.CoreSchema,
|
| 36 |
+
discriminator: str | Discriminator,
|
| 37 |
+
definitions: dict[str, core_schema.CoreSchema] | None = None,
|
| 38 |
+
) -> core_schema.CoreSchema:
|
| 39 |
+
"""Applies the discriminator and returns a new core schema.
|
| 40 |
+
|
| 41 |
+
Args:
|
| 42 |
+
schema: The input schema.
|
| 43 |
+
discriminator: The name of the field which will serve as the discriminator.
|
| 44 |
+
definitions: A mapping of schema ref to schema.
|
| 45 |
+
|
| 46 |
+
Returns:
|
| 47 |
+
The new core schema.
|
| 48 |
+
|
| 49 |
+
Raises:
|
| 50 |
+
TypeError:
|
| 51 |
+
- If `discriminator` is used with invalid union variant.
|
| 52 |
+
- If `discriminator` is used with `Union` type with one variant.
|
| 53 |
+
- If `discriminator` value mapped to multiple choices.
|
| 54 |
+
MissingDefinitionForUnionRef:
|
| 55 |
+
If the definition for ref is missing.
|
| 56 |
+
PydanticUserError:
|
| 57 |
+
- If a model in union doesn't have a discriminator field.
|
| 58 |
+
- If discriminator field has a non-string alias.
|
| 59 |
+
- If discriminator fields have different aliases.
|
| 60 |
+
- If discriminator field not of type `Literal`.
|
| 61 |
+
"""
|
| 62 |
+
from ..types import Discriminator
|
| 63 |
+
|
| 64 |
+
if isinstance(discriminator, Discriminator):
|
| 65 |
+
if isinstance(discriminator.discriminator, str):
|
| 66 |
+
discriminator = discriminator.discriminator
|
| 67 |
+
else:
|
| 68 |
+
return discriminator._convert_schema(schema)
|
| 69 |
+
|
| 70 |
+
return _ApplyInferredDiscriminator(discriminator, definitions or {}).apply(schema)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
class _ApplyInferredDiscriminator:
|
| 74 |
+
"""This class is used to convert an input schema containing a union schema into one where that union is
|
| 75 |
+
replaced with a tagged-union, with all the associated debugging and performance benefits.
|
| 76 |
+
|
| 77 |
+
This is done by:
|
| 78 |
+
* Validating that the input schema is compatible with the provided discriminator
|
| 79 |
+
* Introspecting the schema to determine which discriminator values should map to which union choices
|
| 80 |
+
* Handling various edge cases such as 'definitions', 'default', 'nullable' schemas, and more
|
| 81 |
+
|
| 82 |
+
I have chosen to implement the conversion algorithm in this class, rather than a function,
|
| 83 |
+
to make it easier to maintain state while recursively walking the provided CoreSchema.
|
| 84 |
+
"""
|
| 85 |
+
|
| 86 |
+
def __init__(self, discriminator: str, definitions: dict[str, core_schema.CoreSchema]):
|
| 87 |
+
# `discriminator` should be the name of the field which will serve as the discriminator.
|
| 88 |
+
# It must be the python name of the field, and *not* the field's alias. Note that as of now,
|
| 89 |
+
# all members of a discriminated union _must_ use a field with the same name as the discriminator.
|
| 90 |
+
# This may change if/when we expose a way to manually specify the TaggedUnionSchema's choices.
|
| 91 |
+
self.discriminator = discriminator
|
| 92 |
+
|
| 93 |
+
# `definitions` should contain a mapping of schema ref to schema for all schemas which might
|
| 94 |
+
# be referenced by some choice
|
| 95 |
+
self.definitions = definitions
|
| 96 |
+
|
| 97 |
+
# `_discriminator_alias` will hold the value, if present, of the alias for the discriminator
|
| 98 |
+
#
|
| 99 |
+
# Note: following the v1 implementation, we currently disallow the use of different aliases
|
| 100 |
+
# for different choices. This is not a limitation of pydantic_core, but if we try to handle
|
| 101 |
+
# this, the inference logic gets complicated very quickly, and could result in confusing
|
| 102 |
+
# debugging challenges for users making subtle mistakes.
|
| 103 |
+
#
|
| 104 |
+
# Rather than trying to do the most powerful inference possible, I think we should eventually
|
| 105 |
+
# expose a way to more-manually control the way the TaggedUnionSchema is constructed through
|
| 106 |
+
# the use of a new type which would be placed as an Annotation on the Union type. This would
|
| 107 |
+
# provide the full flexibility/power of pydantic_core's TaggedUnionSchema where necessary for
|
| 108 |
+
# more complex cases, without over-complicating the inference logic for the common cases.
|
| 109 |
+
self._discriminator_alias: str | None = None
|
| 110 |
+
|
| 111 |
+
# `_should_be_nullable` indicates whether the converted union has `None` as an allowed value.
|
| 112 |
+
# If `None` is an acceptable value of the (possibly-wrapped) union, we ignore it while
|
| 113 |
+
# constructing the TaggedUnionSchema, but set the `_should_be_nullable` attribute to True.
|
| 114 |
+
# Once we have constructed the TaggedUnionSchema, if `_should_be_nullable` is True, we ensure
|
| 115 |
+
# that the final schema gets wrapped as a NullableSchema. This has the same semantics on the
|
| 116 |
+
# python side, but resolves the issue that `None` cannot correspond to any discriminator values.
|
| 117 |
+
self._should_be_nullable = False
|
| 118 |
+
|
| 119 |
+
# `_is_nullable` is used to track if the final produced schema will definitely be nullable;
|
| 120 |
+
# we set it to True if the input schema is wrapped in a nullable schema that we know will be preserved
|
| 121 |
+
# as an indication that, even if None is discovered as one of the union choices, we will not need to wrap
|
| 122 |
+
# the final value in another nullable schema.
|
| 123 |
+
#
|
| 124 |
+
# This is more complicated than just checking for the final outermost schema having type 'nullable' thanks
|
| 125 |
+
# to the possible presence of other wrapper schemas such as DefinitionsSchema, WithDefaultSchema, etc.
|
| 126 |
+
self._is_nullable = False
|
| 127 |
+
|
| 128 |
+
# `_choices_to_handle` serves as a stack of choices to add to the tagged union. Initially, choices
|
| 129 |
+
# from the union in the wrapped schema will be appended to this list, and the recursive choice-handling
|
| 130 |
+
# algorithm may add more choices to this stack as (nested) unions are encountered.
|
| 131 |
+
self._choices_to_handle: list[core_schema.CoreSchema] = []
|
| 132 |
+
|
| 133 |
+
# `_tagged_union_choices` is built during the call to `apply`, and will hold the choices to be included
|
| 134 |
+
# in the output TaggedUnionSchema that will replace the union from the input schema
|
| 135 |
+
self._tagged_union_choices: dict[Hashable, core_schema.CoreSchema] = {}
|
| 136 |
+
|
| 137 |
+
# `_used` is changed to True after applying the discriminator to prevent accidental reuse
|
| 138 |
+
self._used = False
|
| 139 |
+
|
| 140 |
+
def apply(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema:
|
| 141 |
+
"""Return a new CoreSchema based on `schema` that uses a tagged-union with the discriminator provided
|
| 142 |
+
to this class.
|
| 143 |
+
|
| 144 |
+
Args:
|
| 145 |
+
schema: The input schema.
|
| 146 |
+
|
| 147 |
+
Returns:
|
| 148 |
+
The new core schema.
|
| 149 |
+
|
| 150 |
+
Raises:
|
| 151 |
+
TypeError:
|
| 152 |
+
- If `discriminator` is used with invalid union variant.
|
| 153 |
+
- If `discriminator` is used with `Union` type with one variant.
|
| 154 |
+
- If `discriminator` value mapped to multiple choices.
|
| 155 |
+
ValueError:
|
| 156 |
+
If the definition for ref is missing.
|
| 157 |
+
PydanticUserError:
|
| 158 |
+
- If a model in union doesn't have a discriminator field.
|
| 159 |
+
- If discriminator field has a non-string alias.
|
| 160 |
+
- If discriminator fields have different aliases.
|
| 161 |
+
- If discriminator field not of type `Literal`.
|
| 162 |
+
"""
|
| 163 |
+
assert not self._used
|
| 164 |
+
schema = self._apply_to_root(schema)
|
| 165 |
+
if self._should_be_nullable and not self._is_nullable:
|
| 166 |
+
schema = core_schema.nullable_schema(schema)
|
| 167 |
+
self._used = True
|
| 168 |
+
return schema
|
| 169 |
+
|
| 170 |
+
def _apply_to_root(self, schema: core_schema.CoreSchema) -> core_schema.CoreSchema:
|
| 171 |
+
"""This method handles the outer-most stage of recursion over the input schema:
|
| 172 |
+
unwrapping nullable or definitions schemas, and calling the `_handle_choice`
|
| 173 |
+
method iteratively on the choices extracted (recursively) from the possibly-wrapped union.
|
| 174 |
+
"""
|
| 175 |
+
if schema['type'] == 'nullable':
|
| 176 |
+
self._is_nullable = True
|
| 177 |
+
wrapped = self._apply_to_root(schema['schema'])
|
| 178 |
+
nullable_wrapper = schema.copy()
|
| 179 |
+
nullable_wrapper['schema'] = wrapped
|
| 180 |
+
return nullable_wrapper
|
| 181 |
+
|
| 182 |
+
if schema['type'] == 'definitions':
|
| 183 |
+
wrapped = self._apply_to_root(schema['schema'])
|
| 184 |
+
definitions_wrapper = schema.copy()
|
| 185 |
+
definitions_wrapper['schema'] = wrapped
|
| 186 |
+
return definitions_wrapper
|
| 187 |
+
|
| 188 |
+
if schema['type'] == 'definition-ref':
|
| 189 |
+
schema_ref = schema['schema_ref']
|
| 190 |
+
if schema_ref not in self.definitions: # pragma: no cover
|
| 191 |
+
raise MissingDefinitionForUnionRef(schema_ref)
|
| 192 |
+
|
| 193 |
+
def_schema = self.definitions[schema_ref]
|
| 194 |
+
# If using a referenceable union as discriminated (e.g. `type Pet = Cat | Dog; field: Pet = Field(discriminator=...)`):
|
| 195 |
+
if def_schema['type'] == 'union':
|
| 196 |
+
schema = def_schema.copy()
|
| 197 |
+
schema.pop('ref')
|
| 198 |
+
|
| 199 |
+
if schema['type'] != 'union':
|
| 200 |
+
# If the schema is not a union, it probably means it just had a single member and
|
| 201 |
+
# was flattened by pydantic_core.
|
| 202 |
+
# However, it still may make sense to apply the discriminator to this schema,
|
| 203 |
+
# as a way to get discriminated-union-style error messages, so we allow this here.
|
| 204 |
+
schema = core_schema.union_schema([schema])
|
| 205 |
+
|
| 206 |
+
# Reverse the choices list before extending the stack so that they get handled in the order they occur
|
| 207 |
+
choices_schemas = [v[0] if isinstance(v, tuple) else v for v in schema['choices'][::-1]]
|
| 208 |
+
self._choices_to_handle.extend(choices_schemas)
|
| 209 |
+
while self._choices_to_handle:
|
| 210 |
+
choice = self._choices_to_handle.pop()
|
| 211 |
+
self._handle_choice(choice)
|
| 212 |
+
|
| 213 |
+
if self._discriminator_alias is not None and self._discriminator_alias != self.discriminator:
|
| 214 |
+
# * We need to annotate `discriminator` as a union here to handle both branches of this conditional
|
| 215 |
+
# * We need to annotate `discriminator` as list[list[str | int]] and not list[list[str]] due to the
|
| 216 |
+
# invariance of list, and because list[list[str | int]] is the type of the discriminator argument
|
| 217 |
+
# to tagged_union_schema below
|
| 218 |
+
# * See the docstring of pydantic_core.core_schema.tagged_union_schema for more details about how to
|
| 219 |
+
# interpret the value of the discriminator argument to tagged_union_schema. (The list[list[str]] here
|
| 220 |
+
# is the appropriate way to provide a list of fallback attributes to check for a discriminator value.)
|
| 221 |
+
discriminator: str | list[list[str | int]] = [[self.discriminator], [self._discriminator_alias]]
|
| 222 |
+
else:
|
| 223 |
+
discriminator = self.discriminator
|
| 224 |
+
return core_schema.tagged_union_schema(
|
| 225 |
+
choices=self._tagged_union_choices,
|
| 226 |
+
discriminator=discriminator,
|
| 227 |
+
custom_error_type=schema.get('custom_error_type'),
|
| 228 |
+
custom_error_message=schema.get('custom_error_message'),
|
| 229 |
+
custom_error_context=schema.get('custom_error_context'),
|
| 230 |
+
strict=False,
|
| 231 |
+
from_attributes=True,
|
| 232 |
+
ref=schema.get('ref'),
|
| 233 |
+
metadata=schema.get('metadata'),
|
| 234 |
+
serialization=schema.get('serialization'),
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
def _handle_choice(self, choice: core_schema.CoreSchema) -> None:
|
| 238 |
+
"""This method handles the "middle" stage of recursion over the input schema.
|
| 239 |
+
Specifically, it is responsible for handling each choice of the outermost union
|
| 240 |
+
(and any "coalesced" choices obtained from inner unions).
|
| 241 |
+
|
| 242 |
+
Here, "handling" entails:
|
| 243 |
+
* Coalescing nested unions and compatible tagged-unions
|
| 244 |
+
* Tracking the presence of 'none' and 'nullable' schemas occurring as choices
|
| 245 |
+
* Validating that each allowed discriminator value maps to a unique choice
|
| 246 |
+
* Updating the _tagged_union_choices mapping that will ultimately be used to build the TaggedUnionSchema.
|
| 247 |
+
"""
|
| 248 |
+
if choice['type'] == 'definition-ref':
|
| 249 |
+
if choice['schema_ref'] not in self.definitions:
|
| 250 |
+
raise MissingDefinitionForUnionRef(choice['schema_ref'])
|
| 251 |
+
|
| 252 |
+
if choice['type'] == 'none':
|
| 253 |
+
self._should_be_nullable = True
|
| 254 |
+
elif choice['type'] == 'definitions':
|
| 255 |
+
self._handle_choice(choice['schema'])
|
| 256 |
+
elif choice['type'] == 'nullable':
|
| 257 |
+
self._should_be_nullable = True
|
| 258 |
+
self._handle_choice(choice['schema']) # unwrap the nullable schema
|
| 259 |
+
elif choice['type'] == 'union':
|
| 260 |
+
# Reverse the choices list before extending the stack so that they get handled in the order they occur
|
| 261 |
+
choices_schemas = [v[0] if isinstance(v, tuple) else v for v in choice['choices'][::-1]]
|
| 262 |
+
self._choices_to_handle.extend(choices_schemas)
|
| 263 |
+
elif choice['type'] not in {
|
| 264 |
+
'model',
|
| 265 |
+
'typed-dict',
|
| 266 |
+
'tagged-union',
|
| 267 |
+
'lax-or-strict',
|
| 268 |
+
'dataclass',
|
| 269 |
+
'dataclass-args',
|
| 270 |
+
'definition-ref',
|
| 271 |
+
} and not _core_utils.is_function_with_inner_schema(choice):
|
| 272 |
+
# We should eventually handle 'definition-ref' as well
|
| 273 |
+
err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.'
|
| 274 |
+
if choice['type'] == 'list':
|
| 275 |
+
err_str += (
|
| 276 |
+
' If you are making use of a list of union types, make sure the discriminator is applied to the '
|
| 277 |
+
'union type and not the list (e.g. `list[Annotated[<T> | <U>, Field(discriminator=...)]]`).'
|
| 278 |
+
)
|
| 279 |
+
raise TypeError(err_str)
|
| 280 |
+
else:
|
| 281 |
+
if choice['type'] == 'tagged-union' and self._is_discriminator_shared(choice):
|
| 282 |
+
# In this case, this inner tagged-union is compatible with the outer tagged-union,
|
| 283 |
+
# and its choices can be coalesced into the outer TaggedUnionSchema.
|
| 284 |
+
subchoices = [x for x in choice['choices'].values() if not isinstance(x, (str, int))]
|
| 285 |
+
# Reverse the choices list before extending the stack so that they get handled in the order they occur
|
| 286 |
+
self._choices_to_handle.extend(subchoices[::-1])
|
| 287 |
+
return
|
| 288 |
+
|
| 289 |
+
inferred_discriminator_values = self._infer_discriminator_values_for_choice(choice, source_name=None)
|
| 290 |
+
self._set_unique_choice_for_values(choice, inferred_discriminator_values)
|
| 291 |
+
|
| 292 |
+
def _is_discriminator_shared(self, choice: core_schema.TaggedUnionSchema) -> bool:
|
| 293 |
+
"""This method returns a boolean indicating whether the discriminator for the `choice`
|
| 294 |
+
is the same as that being used for the outermost tagged union. This is used to
|
| 295 |
+
determine whether this TaggedUnionSchema choice should be "coalesced" into the top level,
|
| 296 |
+
or whether it should be treated as a separate (nested) choice.
|
| 297 |
+
"""
|
| 298 |
+
inner_discriminator = choice['discriminator']
|
| 299 |
+
return inner_discriminator == self.discriminator or (
|
| 300 |
+
isinstance(inner_discriminator, list)
|
| 301 |
+
and (self.discriminator in inner_discriminator or [self.discriminator] in inner_discriminator)
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
def _infer_discriminator_values_for_choice( # noqa C901
|
| 305 |
+
self, choice: core_schema.CoreSchema, source_name: str | None
|
| 306 |
+
) -> list[str | int]:
|
| 307 |
+
"""This function recurses over `choice`, extracting all discriminator values that should map to this choice.
|
| 308 |
+
|
| 309 |
+
`model_name` is accepted for the purpose of producing useful error messages.
|
| 310 |
+
"""
|
| 311 |
+
if choice['type'] == 'definitions':
|
| 312 |
+
return self._infer_discriminator_values_for_choice(choice['schema'], source_name=source_name)
|
| 313 |
+
|
| 314 |
+
elif _core_utils.is_function_with_inner_schema(choice):
|
| 315 |
+
return self._infer_discriminator_values_for_choice(choice['schema'], source_name=source_name)
|
| 316 |
+
|
| 317 |
+
elif choice['type'] == 'lax-or-strict':
|
| 318 |
+
return sorted(
|
| 319 |
+
set(
|
| 320 |
+
self._infer_discriminator_values_for_choice(choice['lax_schema'], source_name=None)
|
| 321 |
+
+ self._infer_discriminator_values_for_choice(choice['strict_schema'], source_name=None)
|
| 322 |
+
)
|
| 323 |
+
)
|
| 324 |
+
|
| 325 |
+
elif choice['type'] == 'tagged-union':
|
| 326 |
+
values: list[str | int] = []
|
| 327 |
+
# Ignore str/int "choices" since these are just references to other choices
|
| 328 |
+
subchoices = [x for x in choice['choices'].values() if not isinstance(x, (str, int))]
|
| 329 |
+
for subchoice in subchoices:
|
| 330 |
+
subchoice_values = self._infer_discriminator_values_for_choice(subchoice, source_name=None)
|
| 331 |
+
values.extend(subchoice_values)
|
| 332 |
+
return values
|
| 333 |
+
|
| 334 |
+
elif choice['type'] == 'union':
|
| 335 |
+
values = []
|
| 336 |
+
for subchoice in choice['choices']:
|
| 337 |
+
subchoice_schema = subchoice[0] if isinstance(subchoice, tuple) else subchoice
|
| 338 |
+
subchoice_values = self._infer_discriminator_values_for_choice(subchoice_schema, source_name=None)
|
| 339 |
+
values.extend(subchoice_values)
|
| 340 |
+
return values
|
| 341 |
+
|
| 342 |
+
elif choice['type'] == 'nullable':
|
| 343 |
+
self._should_be_nullable = True
|
| 344 |
+
return self._infer_discriminator_values_for_choice(choice['schema'], source_name=None)
|
| 345 |
+
|
| 346 |
+
elif choice['type'] == 'model':
|
| 347 |
+
return self._infer_discriminator_values_for_choice(choice['schema'], source_name=choice['cls'].__name__)
|
| 348 |
+
|
| 349 |
+
elif choice['type'] == 'dataclass':
|
| 350 |
+
return self._infer_discriminator_values_for_choice(choice['schema'], source_name=choice['cls'].__name__)
|
| 351 |
+
|
| 352 |
+
elif choice['type'] == 'model-fields':
|
| 353 |
+
return self._infer_discriminator_values_for_model_choice(choice, source_name=source_name)
|
| 354 |
+
|
| 355 |
+
elif choice['type'] == 'dataclass-args':
|
| 356 |
+
return self._infer_discriminator_values_for_dataclass_choice(choice, source_name=source_name)
|
| 357 |
+
|
| 358 |
+
elif choice['type'] == 'typed-dict':
|
| 359 |
+
return self._infer_discriminator_values_for_typed_dict_choice(choice, source_name=source_name)
|
| 360 |
+
|
| 361 |
+
elif choice['type'] == 'definition-ref':
|
| 362 |
+
schema_ref = choice['schema_ref']
|
| 363 |
+
if schema_ref not in self.definitions:
|
| 364 |
+
raise MissingDefinitionForUnionRef(schema_ref)
|
| 365 |
+
return self._infer_discriminator_values_for_choice(self.definitions[schema_ref], source_name=source_name)
|
| 366 |
+
else:
|
| 367 |
+
err_str = f'The core schema type {choice["type"]!r} is not a valid discriminated union variant.'
|
| 368 |
+
if choice['type'] == 'list':
|
| 369 |
+
err_str += (
|
| 370 |
+
' If you are making use of a list of union types, make sure the discriminator is applied to the '
|
| 371 |
+
'union type and not the list (e.g. `list[Annotated[<T> | <U>, Field(discriminator=...)]]`).'
|
| 372 |
+
)
|
| 373 |
+
raise TypeError(err_str)
|
| 374 |
+
|
| 375 |
+
def _infer_discriminator_values_for_typed_dict_choice(
|
| 376 |
+
self, choice: core_schema.TypedDictSchema, source_name: str | None = None
|
| 377 |
+
) -> list[str | int]:
|
| 378 |
+
"""This method just extracts the _infer_discriminator_values_for_choice logic specific to TypedDictSchema
|
| 379 |
+
for the sake of readability.
|
| 380 |
+
"""
|
| 381 |
+
source = 'TypedDict' if source_name is None else f'TypedDict {source_name!r}'
|
| 382 |
+
field = choice['fields'].get(self.discriminator)
|
| 383 |
+
if field is None:
|
| 384 |
+
raise PydanticUserError(
|
| 385 |
+
f'{source} needs a discriminator field for key {self.discriminator!r}', code='discriminator-no-field'
|
| 386 |
+
)
|
| 387 |
+
return self._infer_discriminator_values_for_field(field, source)
|
| 388 |
+
|
| 389 |
+
def _infer_discriminator_values_for_model_choice(
|
| 390 |
+
self, choice: core_schema.ModelFieldsSchema, source_name: str | None = None
|
| 391 |
+
) -> list[str | int]:
|
| 392 |
+
source = 'ModelFields' if source_name is None else f'Model {source_name!r}'
|
| 393 |
+
field = choice['fields'].get(self.discriminator)
|
| 394 |
+
if field is None:
|
| 395 |
+
raise PydanticUserError(
|
| 396 |
+
f'{source} needs a discriminator field for key {self.discriminator!r}', code='discriminator-no-field'
|
| 397 |
+
)
|
| 398 |
+
return self._infer_discriminator_values_for_field(field, source)
|
| 399 |
+
|
| 400 |
+
def _infer_discriminator_values_for_dataclass_choice(
|
| 401 |
+
self, choice: core_schema.DataclassArgsSchema, source_name: str | None = None
|
| 402 |
+
) -> list[str | int]:
|
| 403 |
+
source = 'DataclassArgs' if source_name is None else f'Dataclass {source_name!r}'
|
| 404 |
+
for field in choice['fields']:
|
| 405 |
+
if field['name'] == self.discriminator:
|
| 406 |
+
break
|
| 407 |
+
else:
|
| 408 |
+
raise PydanticUserError(
|
| 409 |
+
f'{source} needs a discriminator field for key {self.discriminator!r}', code='discriminator-no-field'
|
| 410 |
+
)
|
| 411 |
+
return self._infer_discriminator_values_for_field(field, source)
|
| 412 |
+
|
| 413 |
+
def _infer_discriminator_values_for_field(self, field: CoreSchemaField, source: str) -> list[str | int]:
|
| 414 |
+
if field['type'] == 'computed-field':
|
| 415 |
+
# This should never occur as a discriminator, as it is only relevant to serialization
|
| 416 |
+
return []
|
| 417 |
+
alias = field.get('validation_alias', self.discriminator)
|
| 418 |
+
if not isinstance(alias, str):
|
| 419 |
+
raise PydanticUserError(
|
| 420 |
+
f'Alias {alias!r} is not supported in a discriminated union', code='discriminator-alias-type'
|
| 421 |
+
)
|
| 422 |
+
if self._discriminator_alias is None:
|
| 423 |
+
self._discriminator_alias = alias
|
| 424 |
+
elif self._discriminator_alias != alias:
|
| 425 |
+
raise PydanticUserError(
|
| 426 |
+
f'Aliases for discriminator {self.discriminator!r} must be the same '
|
| 427 |
+
f'(got {alias}, {self._discriminator_alias})',
|
| 428 |
+
code='discriminator-alias',
|
| 429 |
+
)
|
| 430 |
+
return self._infer_discriminator_values_for_inner_schema(field['schema'], source)
|
| 431 |
+
|
| 432 |
+
def _infer_discriminator_values_for_inner_schema(
|
| 433 |
+
self, schema: core_schema.CoreSchema, source: str
|
| 434 |
+
) -> list[str | int]:
|
| 435 |
+
"""When inferring discriminator values for a field, we typically extract the expected values from a literal
|
| 436 |
+
schema. This function does that, but also handles nested unions and defaults.
|
| 437 |
+
"""
|
| 438 |
+
if schema['type'] == 'literal':
|
| 439 |
+
return schema['expected']
|
| 440 |
+
|
| 441 |
+
elif schema['type'] == 'union':
|
| 442 |
+
# Generally when multiple values are allowed they should be placed in a single `Literal`, but
|
| 443 |
+
# we add this case to handle the situation where a field is annotated as a `Union` of `Literal`s.
|
| 444 |
+
# For example, this lets us handle `Union[Literal['key'], Union[Literal['Key'], Literal['KEY']]]`
|
| 445 |
+
values: list[Any] = []
|
| 446 |
+
for choice in schema['choices']:
|
| 447 |
+
choice_schema = choice[0] if isinstance(choice, tuple) else choice
|
| 448 |
+
choice_values = self._infer_discriminator_values_for_inner_schema(choice_schema, source)
|
| 449 |
+
values.extend(choice_values)
|
| 450 |
+
return values
|
| 451 |
+
|
| 452 |
+
elif schema['type'] == 'default':
|
| 453 |
+
# This will happen if the field has a default value; we ignore it while extracting the discriminator values
|
| 454 |
+
return self._infer_discriminator_values_for_inner_schema(schema['schema'], source)
|
| 455 |
+
|
| 456 |
+
elif schema['type'] == 'function-after':
|
| 457 |
+
# After validators don't affect the discriminator values
|
| 458 |
+
return self._infer_discriminator_values_for_inner_schema(schema['schema'], source)
|
| 459 |
+
|
| 460 |
+
elif schema['type'] == 'model' and schema.get('root_model'):
|
| 461 |
+
# Support RootModel[Literal[...]] as discriminator field type
|
| 462 |
+
return self._infer_discriminator_values_for_inner_schema(schema['schema'], source)
|
| 463 |
+
|
| 464 |
+
elif schema['type'] in {'function-before', 'function-wrap', 'function-plain'}:
|
| 465 |
+
validator_type = repr(schema['type'].split('-')[1])
|
| 466 |
+
raise PydanticUserError(
|
| 467 |
+
f'Cannot use a mode={validator_type} validator in the'
|
| 468 |
+
f' discriminator field {self.discriminator!r} of {source}',
|
| 469 |
+
code='discriminator-validator',
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
else:
|
| 473 |
+
raise PydanticUserError(
|
| 474 |
+
f'{source} needs field {self.discriminator!r} to be of type `Literal`',
|
| 475 |
+
code='discriminator-needs-literal',
|
| 476 |
+
)
|
| 477 |
+
|
| 478 |
+
def _set_unique_choice_for_values(self, choice: core_schema.CoreSchema, values: Sequence[str | int]) -> None:
|
| 479 |
+
"""This method updates `self.tagged_union_choices` so that all provided (discriminator) `values` map to the
|
| 480 |
+
provided `choice`, validating that none of these values already map to another (different) choice.
|
| 481 |
+
"""
|
| 482 |
+
for discriminator_value in values:
|
| 483 |
+
if discriminator_value in self._tagged_union_choices:
|
| 484 |
+
# It is okay if `value` is already in tagged_union_choices as long as it maps to the same value.
|
| 485 |
+
# Because tagged_union_choices may map values to other values, we need to walk the choices dict
|
| 486 |
+
# until we get to a "real" choice, and confirm that is equal to the one assigned.
|
| 487 |
+
existing_choice = self._tagged_union_choices[discriminator_value]
|
| 488 |
+
if existing_choice != choice:
|
| 489 |
+
raise TypeError(
|
| 490 |
+
f'Value {discriminator_value!r} for discriminator '
|
| 491 |
+
f'{self.discriminator!r} mapped to multiple choices'
|
| 492 |
+
)
|
| 493 |
+
else:
|
| 494 |
+
self._tagged_union_choices[discriminator_value] = choice
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_docs_extraction.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Utilities related to attribute docstring extraction."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ast
|
| 6 |
+
import inspect
|
| 7 |
+
import sys
|
| 8 |
+
import textwrap
|
| 9 |
+
from typing import Any
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class DocstringVisitor(ast.NodeVisitor):
|
| 13 |
+
def __init__(self) -> None:
|
| 14 |
+
super().__init__()
|
| 15 |
+
|
| 16 |
+
self.target: str | None = None
|
| 17 |
+
self.attrs: dict[str, str] = {}
|
| 18 |
+
self.previous_node_type: type[ast.AST] | None = None
|
| 19 |
+
|
| 20 |
+
def visit(self, node: ast.AST) -> Any:
|
| 21 |
+
node_result = super().visit(node)
|
| 22 |
+
self.previous_node_type = type(node)
|
| 23 |
+
return node_result
|
| 24 |
+
|
| 25 |
+
def visit_AnnAssign(self, node: ast.AnnAssign) -> Any:
|
| 26 |
+
if isinstance(node.target, ast.Name):
|
| 27 |
+
self.target = node.target.id
|
| 28 |
+
|
| 29 |
+
def visit_Expr(self, node: ast.Expr) -> Any:
|
| 30 |
+
if (
|
| 31 |
+
isinstance(node.value, ast.Constant)
|
| 32 |
+
and isinstance(node.value.value, str)
|
| 33 |
+
and self.previous_node_type is ast.AnnAssign
|
| 34 |
+
):
|
| 35 |
+
docstring = inspect.cleandoc(node.value.value)
|
| 36 |
+
if self.target:
|
| 37 |
+
self.attrs[self.target] = docstring
|
| 38 |
+
self.target = None
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _dedent_source_lines(source: list[str]) -> str:
|
| 42 |
+
# Required for nested class definitions, e.g. in a function block
|
| 43 |
+
dedent_source = textwrap.dedent(''.join(source))
|
| 44 |
+
if dedent_source.startswith((' ', '\t')):
|
| 45 |
+
# We are in the case where there's a dedented (usually multiline) string
|
| 46 |
+
# at a lower indentation level than the class itself. We wrap our class
|
| 47 |
+
# in a function as a workaround.
|
| 48 |
+
dedent_source = f'def dedent_workaround():\n{dedent_source}'
|
| 49 |
+
return dedent_source
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _extract_source_from_frame(cls: type[Any]) -> list[str] | None:
|
| 53 |
+
frame = inspect.currentframe()
|
| 54 |
+
|
| 55 |
+
while frame:
|
| 56 |
+
if inspect.getmodule(frame) is inspect.getmodule(cls):
|
| 57 |
+
lnum = frame.f_lineno
|
| 58 |
+
try:
|
| 59 |
+
lines, _ = inspect.findsource(frame)
|
| 60 |
+
except OSError: # pragma: no cover
|
| 61 |
+
# Source can't be retrieved (maybe because running in an interactive terminal),
|
| 62 |
+
# we don't want to error here.
|
| 63 |
+
pass
|
| 64 |
+
else:
|
| 65 |
+
block_lines = inspect.getblock(lines[lnum - 1 :])
|
| 66 |
+
dedent_source = _dedent_source_lines(block_lines)
|
| 67 |
+
try:
|
| 68 |
+
block_tree = ast.parse(dedent_source)
|
| 69 |
+
except SyntaxError:
|
| 70 |
+
pass
|
| 71 |
+
else:
|
| 72 |
+
stmt = block_tree.body[0]
|
| 73 |
+
if isinstance(stmt, ast.FunctionDef) and stmt.name == 'dedent_workaround':
|
| 74 |
+
# `_dedent_source_lines` wrapped the class around the workaround function
|
| 75 |
+
stmt = stmt.body[0]
|
| 76 |
+
if isinstance(stmt, ast.ClassDef) and stmt.name == cls.__name__:
|
| 77 |
+
return block_lines
|
| 78 |
+
|
| 79 |
+
frame = frame.f_back
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def extract_docstrings_from_cls(cls: type[Any], use_inspect: bool = False) -> dict[str, str]:
|
| 83 |
+
"""Map model attributes and their corresponding docstring.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
cls: The class of the Pydantic model to inspect.
|
| 87 |
+
use_inspect: Whether to skip usage of frames to find the object and use
|
| 88 |
+
the `inspect` module instead.
|
| 89 |
+
|
| 90 |
+
Returns:
|
| 91 |
+
A mapping containing attribute names and their corresponding docstring.
|
| 92 |
+
"""
|
| 93 |
+
if use_inspect or sys.version_info >= (3, 13):
|
| 94 |
+
# On Python < 3.13, `inspect.getsourcelines()` might not work as expected
|
| 95 |
+
# if two classes have the same name in the same source file.
|
| 96 |
+
# On Python 3.13+, it will use the new `__firstlineno__` class attribute,
|
| 97 |
+
# making it way more robust.
|
| 98 |
+
try:
|
| 99 |
+
source, _ = inspect.getsourcelines(cls)
|
| 100 |
+
except OSError: # pragma: no cover
|
| 101 |
+
return {}
|
| 102 |
+
else:
|
| 103 |
+
# TODO remove this implementation when we drop support for Python 3.12:
|
| 104 |
+
source = _extract_source_from_frame(cls)
|
| 105 |
+
|
| 106 |
+
if not source:
|
| 107 |
+
return {}
|
| 108 |
+
|
| 109 |
+
dedent_source = _dedent_source_lines(source)
|
| 110 |
+
|
| 111 |
+
visitor = DocstringVisitor()
|
| 112 |
+
visitor.visit(ast.parse(dedent_source))
|
| 113 |
+
return visitor.attrs
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_fields.py
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Private logic related to fields (the `Field()` function and `FieldInfo` class), and arguments to `Annotated`."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations as _annotations
|
| 4 |
+
|
| 5 |
+
import dataclasses
|
| 6 |
+
import warnings
|
| 7 |
+
from collections.abc import Mapping
|
| 8 |
+
from functools import cache
|
| 9 |
+
from inspect import Parameter, ismethoddescriptor
|
| 10 |
+
from re import Pattern
|
| 11 |
+
from typing import TYPE_CHECKING, Any, Callable, TypeVar, cast
|
| 12 |
+
|
| 13 |
+
from pydantic_core import PydanticUndefined
|
| 14 |
+
from typing_extensions import TypeIs
|
| 15 |
+
from typing_inspection.introspection import AnnotationSource
|
| 16 |
+
|
| 17 |
+
from pydantic import PydanticDeprecatedSince211
|
| 18 |
+
from pydantic.errors import PydanticUserError
|
| 19 |
+
|
| 20 |
+
from ..aliases import AliasGenerator
|
| 21 |
+
from . import _generics, _typing_extra
|
| 22 |
+
from ._config import ConfigWrapper
|
| 23 |
+
from ._docs_extraction import extract_docstrings_from_cls
|
| 24 |
+
from ._import_utils import import_cached_base_model, import_cached_field_info
|
| 25 |
+
from ._internal_dataclass import slots_true
|
| 26 |
+
from ._namespace_utils import NsResolver
|
| 27 |
+
from ._repr import Representation
|
| 28 |
+
from ._utils import can_be_positional, get_first_not_none
|
| 29 |
+
|
| 30 |
+
if TYPE_CHECKING:
|
| 31 |
+
from annotated_types import BaseMetadata
|
| 32 |
+
|
| 33 |
+
from ..fields import FieldInfo
|
| 34 |
+
from ..main import BaseModel
|
| 35 |
+
from ._dataclasses import PydanticDataclass, StandardDataclass
|
| 36 |
+
from ._decorators import DecoratorInfos
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class PydanticMetadata(Representation):
|
| 40 |
+
"""Base class for annotation markers like `Strict`."""
|
| 41 |
+
|
| 42 |
+
__slots__ = ()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@dataclasses.dataclass(**slots_true) # TODO: make kw_only when we drop support for 3.9.
|
| 46 |
+
class PydanticExtraInfo:
|
| 47 |
+
# TODO: make use of PEP 747:
|
| 48 |
+
annotation: Any
|
| 49 |
+
complete: bool
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def pydantic_general_metadata(**metadata: Any) -> BaseMetadata:
|
| 53 |
+
"""Create a new `_PydanticGeneralMetadata` class with the given metadata.
|
| 54 |
+
|
| 55 |
+
Args:
|
| 56 |
+
**metadata: The metadata to add.
|
| 57 |
+
|
| 58 |
+
Returns:
|
| 59 |
+
The new `_PydanticGeneralMetadata` class.
|
| 60 |
+
"""
|
| 61 |
+
return _general_metadata_cls()(metadata) # type: ignore
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
@cache
|
| 65 |
+
def _general_metadata_cls() -> type[BaseMetadata]:
|
| 66 |
+
"""Do it this way to avoid importing `annotated_types` at import time."""
|
| 67 |
+
from annotated_types import BaseMetadata
|
| 68 |
+
|
| 69 |
+
class _PydanticGeneralMetadata(PydanticMetadata, BaseMetadata):
|
| 70 |
+
"""Pydantic general metadata like `max_digits`."""
|
| 71 |
+
|
| 72 |
+
def __init__(self, metadata: Any):
|
| 73 |
+
self.__dict__ = metadata
|
| 74 |
+
|
| 75 |
+
return _PydanticGeneralMetadata # type: ignore
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def _check_protected_namespaces(
|
| 79 |
+
protected_namespaces: tuple[str | Pattern[str], ...],
|
| 80 |
+
ann_name: str,
|
| 81 |
+
bases: tuple[type[Any], ...],
|
| 82 |
+
cls_name: str,
|
| 83 |
+
) -> None:
|
| 84 |
+
BaseModel = import_cached_base_model()
|
| 85 |
+
|
| 86 |
+
for protected_namespace in protected_namespaces:
|
| 87 |
+
ns_violation = False
|
| 88 |
+
if isinstance(protected_namespace, Pattern):
|
| 89 |
+
ns_violation = protected_namespace.match(ann_name) is not None
|
| 90 |
+
elif isinstance(protected_namespace, str):
|
| 91 |
+
ns_violation = ann_name.startswith(protected_namespace)
|
| 92 |
+
|
| 93 |
+
if ns_violation:
|
| 94 |
+
for b in bases:
|
| 95 |
+
if hasattr(b, ann_name):
|
| 96 |
+
if not (issubclass(b, BaseModel) and ann_name in getattr(b, '__pydantic_fields__', {})):
|
| 97 |
+
raise ValueError(
|
| 98 |
+
f'Field {ann_name!r} conflicts with member {getattr(b, ann_name)}'
|
| 99 |
+
f' of protected namespace {protected_namespace!r}.'
|
| 100 |
+
)
|
| 101 |
+
else:
|
| 102 |
+
valid_namespaces: list[str] = []
|
| 103 |
+
for pn in protected_namespaces:
|
| 104 |
+
if isinstance(pn, Pattern):
|
| 105 |
+
if not pn.match(ann_name):
|
| 106 |
+
valid_namespaces.append(f're.compile({pn.pattern!r})')
|
| 107 |
+
else:
|
| 108 |
+
if not ann_name.startswith(pn):
|
| 109 |
+
valid_namespaces.append(f"'{pn}'")
|
| 110 |
+
|
| 111 |
+
valid_namespaces_str = f'({", ".join(valid_namespaces)}{",)" if len(valid_namespaces) == 1 else ")"}'
|
| 112 |
+
|
| 113 |
+
warnings.warn(
|
| 114 |
+
f'Field {ann_name!r} in {cls_name!r} conflicts with protected namespace {protected_namespace!r}.\n\n'
|
| 115 |
+
f"You may be able to solve this by setting the 'protected_namespaces' configuration to {valid_namespaces_str}.",
|
| 116 |
+
UserWarning,
|
| 117 |
+
stacklevel=5,
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def _update_fields_from_docstrings(cls: type[Any], fields: dict[str, FieldInfo], use_inspect: bool = False) -> None:
|
| 122 |
+
fields_docs = extract_docstrings_from_cls(cls, use_inspect=use_inspect)
|
| 123 |
+
for ann_name, field_info in fields.items():
|
| 124 |
+
if field_info.description is None and ann_name in fields_docs:
|
| 125 |
+
field_info.description = fields_docs[ann_name]
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def _apply_field_title_generator_to_field_info(
|
| 129 |
+
title_generator: Callable[[str, FieldInfo], str],
|
| 130 |
+
field_name: str,
|
| 131 |
+
field_info: FieldInfo,
|
| 132 |
+
):
|
| 133 |
+
if field_info.title is None:
|
| 134 |
+
title = title_generator(field_name, field_info)
|
| 135 |
+
if not isinstance(title, str):
|
| 136 |
+
raise TypeError(f'field_title_generator {title_generator} must return str, not {title.__class__}')
|
| 137 |
+
|
| 138 |
+
field_info.title = title
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _apply_alias_generator_to_field_info(
|
| 142 |
+
alias_generator: Callable[[str], str] | AliasGenerator, field_name: str, field_info: FieldInfo
|
| 143 |
+
):
|
| 144 |
+
"""Apply an alias generator to aliases on a `FieldInfo` instance if appropriate.
|
| 145 |
+
|
| 146 |
+
Args:
|
| 147 |
+
alias_generator: A callable that takes a string and returns a string, or an `AliasGenerator` instance.
|
| 148 |
+
field_name: The name of the field from which to generate the alias.
|
| 149 |
+
field_info: The `FieldInfo` instance to which the alias generator is (maybe) applied.
|
| 150 |
+
"""
|
| 151 |
+
# Apply an alias_generator if
|
| 152 |
+
# 1. An alias is not specified
|
| 153 |
+
# 2. An alias is specified, but the priority is <= 1
|
| 154 |
+
if (
|
| 155 |
+
field_info.alias_priority is None
|
| 156 |
+
or field_info.alias_priority <= 1
|
| 157 |
+
or field_info.alias is None
|
| 158 |
+
or field_info.validation_alias is None
|
| 159 |
+
or field_info.serialization_alias is None
|
| 160 |
+
):
|
| 161 |
+
alias, validation_alias, serialization_alias = None, None, None
|
| 162 |
+
|
| 163 |
+
if isinstance(alias_generator, AliasGenerator):
|
| 164 |
+
alias, validation_alias, serialization_alias = alias_generator.generate_aliases(field_name)
|
| 165 |
+
elif callable(alias_generator):
|
| 166 |
+
alias = alias_generator(field_name)
|
| 167 |
+
if not isinstance(alias, str):
|
| 168 |
+
raise TypeError(f'alias_generator {alias_generator} must return str, not {alias.__class__}')
|
| 169 |
+
|
| 170 |
+
# if priority is not set, we set to 1
|
| 171 |
+
# which supports the case where the alias_generator from a child class is used
|
| 172 |
+
# to generate an alias for a field in a parent class
|
| 173 |
+
if field_info.alias_priority is None or field_info.alias_priority <= 1:
|
| 174 |
+
field_info.alias_priority = 1
|
| 175 |
+
|
| 176 |
+
# if the priority is 1, then we set the aliases to the generated alias
|
| 177 |
+
if field_info.alias_priority == 1:
|
| 178 |
+
field_info.serialization_alias = get_first_not_none(serialization_alias, alias)
|
| 179 |
+
field_info.validation_alias = get_first_not_none(validation_alias, alias)
|
| 180 |
+
field_info.alias = alias
|
| 181 |
+
|
| 182 |
+
# if any of the aliases are not set, then we set them to the corresponding generated alias
|
| 183 |
+
if field_info.alias is None:
|
| 184 |
+
field_info.alias = alias
|
| 185 |
+
if field_info.serialization_alias is None:
|
| 186 |
+
field_info.serialization_alias = get_first_not_none(serialization_alias, alias)
|
| 187 |
+
if field_info.validation_alias is None:
|
| 188 |
+
field_info.validation_alias = get_first_not_none(validation_alias, alias)
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def update_field_from_config(config_wrapper: ConfigWrapper, field_name: str, field_info: FieldInfo) -> None:
|
| 192 |
+
"""Update the `FieldInfo` instance from the configuration set on the model it belongs to.
|
| 193 |
+
|
| 194 |
+
This will apply the title and alias generators from the configuration.
|
| 195 |
+
|
| 196 |
+
Args:
|
| 197 |
+
config_wrapper: The configuration from the model.
|
| 198 |
+
field_name: The field name the `FieldInfo` instance is attached to.
|
| 199 |
+
field_info: The `FieldInfo` instance to update.
|
| 200 |
+
"""
|
| 201 |
+
field_title_generator = field_info.field_title_generator or config_wrapper.field_title_generator
|
| 202 |
+
if field_title_generator is not None:
|
| 203 |
+
_apply_field_title_generator_to_field_info(field_title_generator, field_name, field_info)
|
| 204 |
+
if config_wrapper.alias_generator is not None:
|
| 205 |
+
_apply_alias_generator_to_field_info(config_wrapper.alias_generator, field_name, field_info)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
_deprecated_method_names = {'dict', 'json', 'copy', '_iter', '_copy_and_set_values', '_calculate_keys'}
|
| 209 |
+
|
| 210 |
+
_deprecated_classmethod_names = {
|
| 211 |
+
'parse_obj',
|
| 212 |
+
'parse_raw',
|
| 213 |
+
'parse_file',
|
| 214 |
+
'from_orm',
|
| 215 |
+
'construct',
|
| 216 |
+
'schema',
|
| 217 |
+
'schema_json',
|
| 218 |
+
'validate',
|
| 219 |
+
'update_forward_refs',
|
| 220 |
+
'_get_value',
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def collect_model_fields( # noqa: C901
|
| 225 |
+
cls: type[BaseModel],
|
| 226 |
+
config_wrapper: ConfigWrapper,
|
| 227 |
+
ns_resolver: NsResolver,
|
| 228 |
+
*,
|
| 229 |
+
typevars_map: Mapping[TypeVar, Any] | None = None,
|
| 230 |
+
) -> tuple[dict[str, FieldInfo], PydanticExtraInfo | None, set[str]]:
|
| 231 |
+
"""Collect the fields and class variables names of a nascent Pydantic model.
|
| 232 |
+
|
| 233 |
+
The fields collection process is *lenient*, meaning it won't error if string annotations
|
| 234 |
+
fail to evaluate. If this happens, the original annotation (and assigned value, if any)
|
| 235 |
+
is stored on the created `FieldInfo` instance.
|
| 236 |
+
|
| 237 |
+
The `rebuild_model_fields()` should be called at a later point (e.g. when rebuilding the model),
|
| 238 |
+
and will make use of these stored attributes.
|
| 239 |
+
|
| 240 |
+
Args:
|
| 241 |
+
cls: BaseModel or dataclass.
|
| 242 |
+
config_wrapper: The config wrapper instance.
|
| 243 |
+
ns_resolver: Namespace resolver to use when getting model annotations.
|
| 244 |
+
typevars_map: A dictionary mapping type variables to their concrete types.
|
| 245 |
+
|
| 246 |
+
Returns:
|
| 247 |
+
A three-tuple containing the model fields, the `PydanticExtraInfo` instance if the `__pydantic_extra__` annotation is set,
|
| 248 |
+
and class variables names.
|
| 249 |
+
|
| 250 |
+
Raises:
|
| 251 |
+
NameError:
|
| 252 |
+
- If there is a conflict between a field name and protected namespaces.
|
| 253 |
+
- If there is a field other than `root` in `RootModel`.
|
| 254 |
+
- If a field shadows an attribute in the parent model.
|
| 255 |
+
"""
|
| 256 |
+
FieldInfo_ = import_cached_field_info()
|
| 257 |
+
BaseModel_ = import_cached_base_model()
|
| 258 |
+
|
| 259 |
+
bases = cls.__bases__
|
| 260 |
+
parent_fields_lookup: dict[str, FieldInfo] = {}
|
| 261 |
+
for base in reversed(bases):
|
| 262 |
+
if model_fields := getattr(base, '__pydantic_fields__', None):
|
| 263 |
+
parent_fields_lookup.update(model_fields)
|
| 264 |
+
|
| 265 |
+
type_hints = _typing_extra.get_model_type_hints(cls, ns_resolver=ns_resolver)
|
| 266 |
+
|
| 267 |
+
# `cls_annotations` is only used to determine if an annotation comes from a parent class
|
| 268 |
+
cls_annotations = _typing_extra.safe_get_annotations(cls)
|
| 269 |
+
|
| 270 |
+
fields: dict[str, FieldInfo] = {}
|
| 271 |
+
|
| 272 |
+
class_vars: set[str] = set()
|
| 273 |
+
for ann_name, (ann_type, evaluated) in type_hints.items():
|
| 274 |
+
if ann_name == 'model_config':
|
| 275 |
+
# We never want to treat `model_config` as a field
|
| 276 |
+
# Note: we may need to change this logic if/when we introduce a `BareModel` class with no
|
| 277 |
+
# protected namespaces (where `model_config` might be allowed as a field name)
|
| 278 |
+
continue
|
| 279 |
+
|
| 280 |
+
_check_protected_namespaces(
|
| 281 |
+
protected_namespaces=config_wrapper.protected_namespaces,
|
| 282 |
+
ann_name=ann_name,
|
| 283 |
+
bases=bases,
|
| 284 |
+
cls_name=cls.__name__,
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
if _typing_extra.is_classvar_annotation(ann_type):
|
| 288 |
+
class_vars.add(ann_name)
|
| 289 |
+
continue
|
| 290 |
+
|
| 291 |
+
assigned_value = getattr(cls, ann_name, PydanticUndefined)
|
| 292 |
+
if assigned_value is not PydanticUndefined and (
|
| 293 |
+
# One of the deprecated instance methods was used as a field name (e.g. `dict()`):
|
| 294 |
+
any(getattr(BaseModel_, depr_name, None) is assigned_value for depr_name in _deprecated_method_names)
|
| 295 |
+
# One of the deprecated class methods was used as a field name (e.g. `schema()`):
|
| 296 |
+
or (
|
| 297 |
+
hasattr(assigned_value, '__func__')
|
| 298 |
+
and any(
|
| 299 |
+
getattr(getattr(BaseModel_, depr_name, None), '__func__', None) is assigned_value.__func__ # pyright: ignore[reportAttributeAccessIssue]
|
| 300 |
+
for depr_name in _deprecated_classmethod_names
|
| 301 |
+
)
|
| 302 |
+
)
|
| 303 |
+
):
|
| 304 |
+
# Then `assigned_value` would be the method, even though no default was specified:
|
| 305 |
+
assigned_value = PydanticUndefined
|
| 306 |
+
|
| 307 |
+
if not is_valid_field_name(ann_name):
|
| 308 |
+
continue
|
| 309 |
+
if cls.__pydantic_root_model__ and ann_name != 'root':
|
| 310 |
+
raise NameError(
|
| 311 |
+
f"Unexpected field with name {ann_name!r}; only 'root' is allowed as a field of a `RootModel`"
|
| 312 |
+
)
|
| 313 |
+
|
| 314 |
+
for base in bases:
|
| 315 |
+
if hasattr(base, ann_name):
|
| 316 |
+
if ann_name not in cls_annotations:
|
| 317 |
+
# Don't warn when a field exists in a parent class but has not been defined in the current class
|
| 318 |
+
continue
|
| 319 |
+
|
| 320 |
+
# when building a generic model with `MyModel[int]`, the generic_origin check makes sure we don't get
|
| 321 |
+
# "... shadows an attribute" warnings
|
| 322 |
+
generic_origin = getattr(cls, '__pydantic_generic_metadata__', {}).get('origin')
|
| 323 |
+
if base is generic_origin:
|
| 324 |
+
# Don't warn when "shadowing" of attributes in parametrized generics
|
| 325 |
+
continue
|
| 326 |
+
|
| 327 |
+
dataclass_fields = {
|
| 328 |
+
field.name for field in (dataclasses.fields(base) if dataclasses.is_dataclass(base) else ())
|
| 329 |
+
}
|
| 330 |
+
if ann_name in dataclass_fields:
|
| 331 |
+
# Don't warn when inheriting stdlib dataclasses whose fields are "shadowed" by defaults being set
|
| 332 |
+
# on the class instance.
|
| 333 |
+
continue
|
| 334 |
+
|
| 335 |
+
warnings.warn(
|
| 336 |
+
f'Field name "{ann_name}" in "{cls.__qualname__}" shadows an attribute in parent '
|
| 337 |
+
f'"{base.__qualname__}"',
|
| 338 |
+
UserWarning,
|
| 339 |
+
stacklevel=4,
|
| 340 |
+
)
|
| 341 |
+
|
| 342 |
+
if assigned_value is PydanticUndefined: # no assignment, just a plain annotation
|
| 343 |
+
if ann_name in cls_annotations or ann_name not in parent_fields_lookup:
|
| 344 |
+
# field is either:
|
| 345 |
+
# - present in the current model's annotations (and *not* from parent classes)
|
| 346 |
+
# - not found on any base classes; this seems to be caused by fields not getting
|
| 347 |
+
# generated due to models not being fully defined while initializing recursive models.
|
| 348 |
+
# Nothing stops us from just creating a `FieldInfo` for this type hint, so we do this.
|
| 349 |
+
field_info = FieldInfo_.from_annotation(ann_type, _source=AnnotationSource.CLASS)
|
| 350 |
+
field_info._original_annotation = ann_type
|
| 351 |
+
if not evaluated:
|
| 352 |
+
field_info._complete = False
|
| 353 |
+
# Store the original annotation that should be used to rebuild
|
| 354 |
+
# the field info later:
|
| 355 |
+
else:
|
| 356 |
+
# The field was present on one of the (possibly multiple) base classes, we make a copy directly from it.
|
| 357 |
+
parent_field_info = parent_fields_lookup[ann_name]._copy()
|
| 358 |
+
|
| 359 |
+
# The only case where substituting the type variables is relevant (i.e. when `typevars_map` is not empty)
|
| 360 |
+
# is when a generic class is parameterized (e.g. `MyGenericModel[int, str]`), which creates a new class object
|
| 361 |
+
# (unlike the stdlib genercis that create a generic alias). In this case, we are guaranteed to only have to copy
|
| 362 |
+
# from the origin/parent model (e.g. `MyGenericModel`).
|
| 363 |
+
if typevars_map:
|
| 364 |
+
field_info = _recreate_field_info(
|
| 365 |
+
parent_field_info, ns_resolver=ns_resolver, typevars_map=typevars_map, lenient=True
|
| 366 |
+
)
|
| 367 |
+
else:
|
| 368 |
+
field_info = parent_field_info
|
| 369 |
+
|
| 370 |
+
else: # An assigned value is present (either the default value, or a `Field()` function)
|
| 371 |
+
if isinstance(assigned_value, FieldInfo_) and ismethoddescriptor(assigned_value.default):
|
| 372 |
+
# `assigned_value` was fetched using `getattr`, which triggers a call to `__get__`
|
| 373 |
+
# for descriptors, so we do the same if the `= field(default=...)` form is used.
|
| 374 |
+
# Note that we only do this for method descriptors for now, we might want to
|
| 375 |
+
# extend this to any descriptor in the future (by simply checking for
|
| 376 |
+
# `hasattr(assigned_value.default, '__get__')`).
|
| 377 |
+
default = assigned_value.default.__get__(None, cls)
|
| 378 |
+
assigned_value.default = default
|
| 379 |
+
assigned_value._attributes_set['default'] = default
|
| 380 |
+
|
| 381 |
+
field_info = FieldInfo_.from_annotated_attribute(ann_type, assigned_value, _source=AnnotationSource.CLASS)
|
| 382 |
+
|
| 383 |
+
# Store the original annotation and assignment value that could be used to rebuild the field info later.
|
| 384 |
+
field_info._original_assignment = assigned_value
|
| 385 |
+
field_info._original_annotation = ann_type
|
| 386 |
+
if not evaluated:
|
| 387 |
+
field_info._complete = False
|
| 388 |
+
elif 'final' in field_info._qualifiers and not field_info.is_required():
|
| 389 |
+
warnings.warn(
|
| 390 |
+
f'Annotation {ann_name!r} is marked as final and has a default value. Pydantic treats {ann_name!r} as a '
|
| 391 |
+
'class variable, but it will be considered as a normal field in V3 to be aligned with dataclasses. If you '
|
| 392 |
+
f'still want {ann_name!r} to be considered as a class variable, annotate it as: `ClassVar[<type>] = <default>.`',
|
| 393 |
+
category=PydanticDeprecatedSince211,
|
| 394 |
+
# Incorrect when `create_model` is used, but the chance that final with a default is used is low in that case:
|
| 395 |
+
stacklevel=4,
|
| 396 |
+
)
|
| 397 |
+
class_vars.add(ann_name)
|
| 398 |
+
continue
|
| 399 |
+
|
| 400 |
+
# attributes which are fields are removed from the class namespace:
|
| 401 |
+
# 1. To match the behaviour of annotation-only fields
|
| 402 |
+
# 2. To avoid false positives in the NameError check above
|
| 403 |
+
try:
|
| 404 |
+
delattr(cls, ann_name)
|
| 405 |
+
except AttributeError:
|
| 406 |
+
pass # indicates the attribute was on a parent class
|
| 407 |
+
|
| 408 |
+
# Use cls.__dict__['__pydantic_decorators__'] instead of cls.__pydantic_decorators__
|
| 409 |
+
# to make sure the decorators have already been built for this exact class
|
| 410 |
+
decorators: DecoratorInfos = cls.__dict__['__pydantic_decorators__']
|
| 411 |
+
if ann_name in decorators.computed_fields:
|
| 412 |
+
raise TypeError(
|
| 413 |
+
f'Field {ann_name!r} of class {cls.__name__!r} overrides symbol of same name in a parent class. '
|
| 414 |
+
'This override with a computed_field is incompatible.'
|
| 415 |
+
)
|
| 416 |
+
fields[ann_name] = field_info
|
| 417 |
+
|
| 418 |
+
if field_info._complete:
|
| 419 |
+
# If not complete, this will be called in `rebuild_model_fields()`:
|
| 420 |
+
update_field_from_config(config_wrapper, ann_name, field_info)
|
| 421 |
+
|
| 422 |
+
if config_wrapper.use_attribute_docstrings:
|
| 423 |
+
_update_fields_from_docstrings(cls, fields)
|
| 424 |
+
|
| 425 |
+
pydantic_extra_info: PydanticExtraInfo | None = None
|
| 426 |
+
if '__pydantic_extra__' in type_hints:
|
| 427 |
+
ann, complete = type_hints['__pydantic_extra__']
|
| 428 |
+
pydantic_extra_info = PydanticExtraInfo(
|
| 429 |
+
annotation=ann,
|
| 430 |
+
complete=complete,
|
| 431 |
+
)
|
| 432 |
+
|
| 433 |
+
return fields, pydantic_extra_info, class_vars
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def rebuild_model_fields(
|
| 437 |
+
cls: type[BaseModel],
|
| 438 |
+
*,
|
| 439 |
+
config_wrapper: ConfigWrapper,
|
| 440 |
+
ns_resolver: NsResolver,
|
| 441 |
+
typevars_map: Mapping[TypeVar, Any],
|
| 442 |
+
) -> tuple[dict[str, FieldInfo], PydanticExtraInfo | None]:
|
| 443 |
+
"""Rebuild the (already present) model fields by trying to reevaluate annotations.
|
| 444 |
+
|
| 445 |
+
This function should be called whenever a model with incomplete fields is encountered.
|
| 446 |
+
|
| 447 |
+
Returns:
|
| 448 |
+
A two-tuple, the first element being the rebuilt fields, the second element being
|
| 449 |
+
the rebuild `PydanticExtraInfo` instance, if available.
|
| 450 |
+
|
| 451 |
+
Raises:
|
| 452 |
+
NameError: If one of the annotations failed to evaluate.
|
| 453 |
+
|
| 454 |
+
Note:
|
| 455 |
+
This function *doesn't* mutate the model fields in place, as it can be called during
|
| 456 |
+
schema generation, where you don't want to mutate other model's fields.
|
| 457 |
+
"""
|
| 458 |
+
rebuilt_fields: dict[str, FieldInfo] = {}
|
| 459 |
+
with ns_resolver.push(cls):
|
| 460 |
+
for f_name, field_info in cls.__pydantic_fields__.items():
|
| 461 |
+
if field_info._complete:
|
| 462 |
+
rebuilt_fields[f_name] = field_info
|
| 463 |
+
else:
|
| 464 |
+
new_field = _recreate_field_info(
|
| 465 |
+
field_info, ns_resolver=ns_resolver, typevars_map=typevars_map, lenient=False
|
| 466 |
+
)
|
| 467 |
+
update_field_from_config(config_wrapper, f_name, new_field)
|
| 468 |
+
rebuilt_fields[f_name] = new_field
|
| 469 |
+
|
| 470 |
+
if cls.__pydantic_extra_info__ is not None and not cls.__pydantic_extra_info__.complete:
|
| 471 |
+
rebuilt_extra_info = PydanticExtraInfo(
|
| 472 |
+
annotation=_typing_extra.eval_type(
|
| 473 |
+
cls.__pydantic_extra_info__.annotation, *ns_resolver.types_namespace
|
| 474 |
+
),
|
| 475 |
+
complete=True,
|
| 476 |
+
)
|
| 477 |
+
else:
|
| 478 |
+
rebuilt_extra_info = cls.__pydantic_extra_info__
|
| 479 |
+
|
| 480 |
+
return rebuilt_fields, rebuilt_extra_info
|
| 481 |
+
|
| 482 |
+
|
| 483 |
+
def _recreate_field_info(
|
| 484 |
+
field_info: FieldInfo,
|
| 485 |
+
ns_resolver: NsResolver,
|
| 486 |
+
typevars_map: Mapping[TypeVar, Any],
|
| 487 |
+
*,
|
| 488 |
+
lenient: bool,
|
| 489 |
+
) -> FieldInfo:
|
| 490 |
+
FieldInfo_ = import_cached_field_info()
|
| 491 |
+
|
| 492 |
+
existing_desc = field_info.description
|
| 493 |
+
if lenient:
|
| 494 |
+
ann = _generics.replace_types(field_info._original_annotation, typevars_map)
|
| 495 |
+
ann, evaluated = _typing_extra.try_eval_type(
|
| 496 |
+
ann,
|
| 497 |
+
*ns_resolver.types_namespace,
|
| 498 |
+
)
|
| 499 |
+
else:
|
| 500 |
+
# Not the best pattern, maybe we could ship our own `eval_type()`,
|
| 501 |
+
# that would replace the type variables on the fly during evaluation.
|
| 502 |
+
ann = _typing_extra.eval_type(
|
| 503 |
+
field_info._original_annotation,
|
| 504 |
+
*ns_resolver.types_namespace,
|
| 505 |
+
)
|
| 506 |
+
ann = _generics.replace_types(ann, typevars_map)
|
| 507 |
+
ann = _typing_extra.eval_type(
|
| 508 |
+
ann,
|
| 509 |
+
*ns_resolver.types_namespace,
|
| 510 |
+
)
|
| 511 |
+
evaluated = True
|
| 512 |
+
|
| 513 |
+
if (assign := field_info._original_assignment) is PydanticUndefined:
|
| 514 |
+
new_field = FieldInfo_.from_annotation(ann, _source=AnnotationSource.CLASS)
|
| 515 |
+
else:
|
| 516 |
+
new_field = FieldInfo_.from_annotated_attribute(ann, assign, _source=AnnotationSource.CLASS)
|
| 517 |
+
new_field._original_assignment = assign
|
| 518 |
+
new_field._original_annotation = ann
|
| 519 |
+
# The description might come from the docstring if `use_attribute_docstrings` was `True`:
|
| 520 |
+
new_field.description = new_field.description if new_field.description is not None else existing_desc
|
| 521 |
+
if not evaluated:
|
| 522 |
+
new_field._complete = False
|
| 523 |
+
|
| 524 |
+
return new_field
|
| 525 |
+
|
| 526 |
+
|
| 527 |
+
def collect_dataclass_fields(
|
| 528 |
+
cls: type[StandardDataclass],
|
| 529 |
+
*,
|
| 530 |
+
config_wrapper: ConfigWrapper,
|
| 531 |
+
ns_resolver: NsResolver | None = None,
|
| 532 |
+
typevars_map: dict[Any, Any] | None = None,
|
| 533 |
+
) -> dict[str, FieldInfo]:
|
| 534 |
+
"""Collect the fields of a dataclass.
|
| 535 |
+
|
| 536 |
+
Args:
|
| 537 |
+
cls: dataclass.
|
| 538 |
+
config_wrapper: The config wrapper instance.
|
| 539 |
+
ns_resolver: Namespace resolver to use when getting dataclass annotations.
|
| 540 |
+
Defaults to an empty instance.
|
| 541 |
+
typevars_map: A dictionary mapping type variables to their concrete types.
|
| 542 |
+
|
| 543 |
+
Returns:
|
| 544 |
+
The dataclass fields.
|
| 545 |
+
"""
|
| 546 |
+
FieldInfo_ = import_cached_field_info()
|
| 547 |
+
|
| 548 |
+
fields: dict[str, FieldInfo] = {}
|
| 549 |
+
ns_resolver = ns_resolver or NsResolver()
|
| 550 |
+
dataclass_fields = cls.__dataclass_fields__
|
| 551 |
+
|
| 552 |
+
# The logic here is similar to `_typing_extra.get_cls_type_hints`,
|
| 553 |
+
# although we do it manually as stdlib dataclasses already have annotations
|
| 554 |
+
# collected in each class:
|
| 555 |
+
for base in reversed(cls.__mro__):
|
| 556 |
+
if not dataclasses.is_dataclass(base):
|
| 557 |
+
continue
|
| 558 |
+
|
| 559 |
+
with ns_resolver.push(base):
|
| 560 |
+
for ann_name, dataclass_field in dataclass_fields.items():
|
| 561 |
+
base_anns = _typing_extra.safe_get_annotations(base)
|
| 562 |
+
|
| 563 |
+
if ann_name not in base_anns:
|
| 564 |
+
# `__dataclass_fields__`contains every field, even the ones from base classes.
|
| 565 |
+
# Only collect the ones defined on `base`.
|
| 566 |
+
continue
|
| 567 |
+
|
| 568 |
+
globalns, localns = ns_resolver.types_namespace
|
| 569 |
+
ann_type, evaluated = _typing_extra.try_eval_type(dataclass_field.type, globalns, localns)
|
| 570 |
+
|
| 571 |
+
if _typing_extra.is_classvar_annotation(ann_type):
|
| 572 |
+
continue
|
| 573 |
+
|
| 574 |
+
if (
|
| 575 |
+
not dataclass_field.init
|
| 576 |
+
and dataclass_field.default is dataclasses.MISSING
|
| 577 |
+
and dataclass_field.default_factory is dataclasses.MISSING
|
| 578 |
+
):
|
| 579 |
+
# TODO: We should probably do something with this so that validate_assignment behaves properly
|
| 580 |
+
# Issue: https://github.com/pydantic/pydantic/issues/5470
|
| 581 |
+
continue
|
| 582 |
+
|
| 583 |
+
if isinstance(dataclass_field.default, FieldInfo_):
|
| 584 |
+
if dataclass_field.default.init_var:
|
| 585 |
+
if dataclass_field.default.init is False:
|
| 586 |
+
raise PydanticUserError(
|
| 587 |
+
f'Dataclass field {ann_name} has init=False and init_var=True, but these are mutually exclusive.',
|
| 588 |
+
code='clashing-init-and-init-var',
|
| 589 |
+
)
|
| 590 |
+
|
| 591 |
+
# TODO: same note as above re validate_assignment
|
| 592 |
+
continue
|
| 593 |
+
field_info = FieldInfo_.from_annotated_attribute(
|
| 594 |
+
ann_type, dataclass_field.default, _source=AnnotationSource.DATACLASS
|
| 595 |
+
)
|
| 596 |
+
field_info._original_assignment = dataclass_field.default
|
| 597 |
+
else:
|
| 598 |
+
field_info = FieldInfo_.from_annotated_attribute(
|
| 599 |
+
ann_type, dataclass_field, _source=AnnotationSource.DATACLASS
|
| 600 |
+
)
|
| 601 |
+
field_info._original_assignment = dataclass_field
|
| 602 |
+
|
| 603 |
+
if not evaluated:
|
| 604 |
+
field_info._complete = False
|
| 605 |
+
field_info._original_annotation = ann_type
|
| 606 |
+
|
| 607 |
+
fields[ann_name] = field_info
|
| 608 |
+
update_field_from_config(config_wrapper, ann_name, field_info)
|
| 609 |
+
|
| 610 |
+
if field_info.default is not PydanticUndefined and isinstance(
|
| 611 |
+
getattr(cls, ann_name, field_info), FieldInfo_
|
| 612 |
+
):
|
| 613 |
+
# We need this to fix the default when the "default" from __dataclass_fields__ is a pydantic.FieldInfo
|
| 614 |
+
setattr(cls, ann_name, field_info.default)
|
| 615 |
+
|
| 616 |
+
if typevars_map:
|
| 617 |
+
for field in fields.values():
|
| 618 |
+
# We don't pass any ns, as `field.annotation`
|
| 619 |
+
# was already evaluated. TODO: is this method relevant?
|
| 620 |
+
# Can't we juste use `_generics.replace_types`?
|
| 621 |
+
field.apply_typevars_map(typevars_map)
|
| 622 |
+
|
| 623 |
+
if config_wrapper.use_attribute_docstrings:
|
| 624 |
+
_update_fields_from_docstrings(
|
| 625 |
+
cls,
|
| 626 |
+
fields,
|
| 627 |
+
# We can't rely on the (more reliable) frame inspection method
|
| 628 |
+
# for stdlib dataclasses:
|
| 629 |
+
use_inspect=not hasattr(cls, '__is_pydantic_dataclass__'),
|
| 630 |
+
)
|
| 631 |
+
|
| 632 |
+
return fields
|
| 633 |
+
|
| 634 |
+
|
| 635 |
+
def rebuild_dataclass_fields(
|
| 636 |
+
cls: type[PydanticDataclass],
|
| 637 |
+
*,
|
| 638 |
+
config_wrapper: ConfigWrapper,
|
| 639 |
+
ns_resolver: NsResolver,
|
| 640 |
+
typevars_map: Mapping[TypeVar, Any],
|
| 641 |
+
) -> dict[str, FieldInfo]:
|
| 642 |
+
"""Rebuild the (already present) dataclass fields by trying to reevaluate annotations.
|
| 643 |
+
|
| 644 |
+
This function should be called whenever a dataclass with incomplete fields is encountered.
|
| 645 |
+
|
| 646 |
+
Raises:
|
| 647 |
+
NameError: If one of the annotations failed to evaluate.
|
| 648 |
+
|
| 649 |
+
Note:
|
| 650 |
+
This function *doesn't* mutate the dataclass fields in place, as it can be called during
|
| 651 |
+
schema generation, where you don't want to mutate other dataclass's fields.
|
| 652 |
+
"""
|
| 653 |
+
FieldInfo_ = import_cached_field_info()
|
| 654 |
+
|
| 655 |
+
rebuilt_fields: dict[str, FieldInfo] = {}
|
| 656 |
+
with ns_resolver.push(cls):
|
| 657 |
+
for f_name, field_info in cls.__pydantic_fields__.items():
|
| 658 |
+
if field_info._complete:
|
| 659 |
+
rebuilt_fields[f_name] = field_info
|
| 660 |
+
else:
|
| 661 |
+
existing_desc = field_info.description
|
| 662 |
+
ann = _typing_extra.eval_type(
|
| 663 |
+
field_info._original_annotation,
|
| 664 |
+
*ns_resolver.types_namespace,
|
| 665 |
+
)
|
| 666 |
+
ann = _generics.replace_types(ann, typevars_map)
|
| 667 |
+
new_field = FieldInfo_.from_annotated_attribute(
|
| 668 |
+
ann,
|
| 669 |
+
field_info._original_assignment,
|
| 670 |
+
_source=AnnotationSource.DATACLASS,
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
# The description might come from the docstring if `use_attribute_docstrings` was `True`:
|
| 674 |
+
new_field.description = new_field.description if new_field.description is not None else existing_desc
|
| 675 |
+
update_field_from_config(config_wrapper, f_name, new_field)
|
| 676 |
+
rebuilt_fields[f_name] = new_field
|
| 677 |
+
|
| 678 |
+
return rebuilt_fields
|
| 679 |
+
|
| 680 |
+
|
| 681 |
+
def is_valid_field_name(name: str) -> bool:
|
| 682 |
+
return not name.startswith('_')
|
| 683 |
+
|
| 684 |
+
|
| 685 |
+
def is_valid_privateattr_name(name: str) -> bool:
|
| 686 |
+
return name.startswith('_') and not name.startswith('__')
|
| 687 |
+
|
| 688 |
+
|
| 689 |
+
def takes_validated_data_argument(
|
| 690 |
+
default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any],
|
| 691 |
+
) -> TypeIs[Callable[[dict[str, Any]], Any]]:
|
| 692 |
+
"""Whether the provided default factory callable has a validated data parameter."""
|
| 693 |
+
try:
|
| 694 |
+
sig = _typing_extra.signature_no_eval(default_factory)
|
| 695 |
+
except (ValueError, TypeError):
|
| 696 |
+
# `inspect.signature` might not be able to infer a signature, e.g. with C objects.
|
| 697 |
+
# In this case, we assume no data argument is present:
|
| 698 |
+
return False
|
| 699 |
+
|
| 700 |
+
parameters = list(sig.parameters.values())
|
| 701 |
+
|
| 702 |
+
return len(parameters) == 1 and can_be_positional(parameters[0]) and parameters[0].default is Parameter.empty
|
| 703 |
+
|
| 704 |
+
|
| 705 |
+
def resolve_default_value(
|
| 706 |
+
default: Any,
|
| 707 |
+
default_factory: Callable[[], Any] | Callable[[dict[str, Any]], Any] | None,
|
| 708 |
+
*,
|
| 709 |
+
validated_data: dict[str, Any] | None = None,
|
| 710 |
+
call_default_factory: bool = False,
|
| 711 |
+
) -> Any:
|
| 712 |
+
"""Resolve the default value using either a static default or a default_factory."""
|
| 713 |
+
from ._utils import smart_deepcopy
|
| 714 |
+
|
| 715 |
+
if default_factory is None:
|
| 716 |
+
return smart_deepcopy(default)
|
| 717 |
+
if call_default_factory:
|
| 718 |
+
if takes_validated_data_argument(default_factory=default_factory):
|
| 719 |
+
fac = cast('Callable[[dict[str, Any]], Any]', default_factory)
|
| 720 |
+
if validated_data is None:
|
| 721 |
+
raise ValueError(
|
| 722 |
+
"The default factory requires the 'validated_data' argument, which was not provided when calling 'get_default()'."
|
| 723 |
+
)
|
| 724 |
+
return fac(validated_data)
|
| 725 |
+
else:
|
| 726 |
+
fac = cast('Callable[[], Any]', default_factory)
|
| 727 |
+
return fac()
|
| 728 |
+
|
| 729 |
+
return PydanticUndefined
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_forward_ref.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations as _annotations
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
from typing import Union
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
@dataclass
|
| 8 |
+
class PydanticRecursiveRef:
|
| 9 |
+
type_ref: str
|
| 10 |
+
|
| 11 |
+
__name__ = 'PydanticRecursiveRef'
|
| 12 |
+
__hash__ = object.__hash__
|
| 13 |
+
|
| 14 |
+
def __call__(self) -> None:
|
| 15 |
+
"""Defining __call__ is necessary for the `typing` module to let you use an instance of
|
| 16 |
+
this class as the result of resolving a standard ForwardRef.
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
def __or__(self, other):
|
| 20 |
+
return Union[self, other] # type: ignore
|
| 21 |
+
|
| 22 |
+
def __ror__(self, other):
|
| 23 |
+
return Union[other, self] # type: ignore
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_generate_schema.py
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_generics.py
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import operator
|
| 4 |
+
import sys
|
| 5 |
+
import types
|
| 6 |
+
import typing
|
| 7 |
+
from collections import ChainMap
|
| 8 |
+
from collections.abc import Iterator, Mapping
|
| 9 |
+
from contextlib import contextmanager
|
| 10 |
+
from contextvars import ContextVar
|
| 11 |
+
from functools import reduce
|
| 12 |
+
from itertools import zip_longest
|
| 13 |
+
from types import prepare_class
|
| 14 |
+
from typing import TYPE_CHECKING, Annotated, Any, TypedDict, TypeVar, cast
|
| 15 |
+
from weakref import WeakValueDictionary
|
| 16 |
+
|
| 17 |
+
import typing_extensions
|
| 18 |
+
from typing_inspection import typing_objects
|
| 19 |
+
from typing_inspection.introspection import is_union_origin
|
| 20 |
+
|
| 21 |
+
from . import _typing_extra
|
| 22 |
+
from ._core_utils import get_type_ref
|
| 23 |
+
from ._forward_ref import PydanticRecursiveRef
|
| 24 |
+
from ._utils import all_identical, is_model_class
|
| 25 |
+
|
| 26 |
+
if TYPE_CHECKING:
|
| 27 |
+
from ..main import BaseModel
|
| 28 |
+
|
| 29 |
+
GenericTypesCacheKey = tuple[Any, Any, tuple[Any, ...]]
|
| 30 |
+
|
| 31 |
+
# Note: We want to remove LimitedDict, but to do this, we'd need to improve the handling of generics caching.
|
| 32 |
+
# Right now, to handle recursive generics, we some types must remain cached for brief periods without references.
|
| 33 |
+
# By chaining the WeakValuesDict with a LimitedDict, we have a way to retain caching for all types with references,
|
| 34 |
+
# while also retaining a limited number of types even without references. This is generally enough to build
|
| 35 |
+
# specific recursive generic models without losing required items out of the cache.
|
| 36 |
+
|
| 37 |
+
KT = TypeVar('KT')
|
| 38 |
+
VT = TypeVar('VT')
|
| 39 |
+
_LIMITED_DICT_SIZE = 100
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class LimitedDict(dict[KT, VT]):
|
| 43 |
+
def __init__(self, size_limit: int = _LIMITED_DICT_SIZE) -> None:
|
| 44 |
+
self.size_limit = size_limit
|
| 45 |
+
super().__init__()
|
| 46 |
+
|
| 47 |
+
def __setitem__(self, key: KT, value: VT, /) -> None:
|
| 48 |
+
super().__setitem__(key, value)
|
| 49 |
+
if len(self) > self.size_limit:
|
| 50 |
+
excess = len(self) - self.size_limit + self.size_limit // 10
|
| 51 |
+
to_remove = list(self.keys())[:excess]
|
| 52 |
+
for k in to_remove:
|
| 53 |
+
del self[k]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
# weak dictionaries allow the dynamically created parametrized versions of generic models to get collected
|
| 57 |
+
# once they are no longer referenced by the caller.
|
| 58 |
+
GenericTypesCache = WeakValueDictionary[GenericTypesCacheKey, 'type[BaseModel]']
|
| 59 |
+
|
| 60 |
+
if TYPE_CHECKING:
|
| 61 |
+
|
| 62 |
+
class DeepChainMap(ChainMap[KT, VT]): # type: ignore
|
| 63 |
+
...
|
| 64 |
+
|
| 65 |
+
else:
|
| 66 |
+
|
| 67 |
+
class DeepChainMap(ChainMap):
|
| 68 |
+
"""Variant of ChainMap that allows direct updates to inner scopes.
|
| 69 |
+
|
| 70 |
+
Taken from https://docs.python.org/3/library/collections.html#collections.ChainMap,
|
| 71 |
+
with some light modifications for this use case.
|
| 72 |
+
"""
|
| 73 |
+
|
| 74 |
+
def clear(self) -> None:
|
| 75 |
+
for mapping in self.maps:
|
| 76 |
+
mapping.clear()
|
| 77 |
+
|
| 78 |
+
def __setitem__(self, key: KT, value: VT) -> None:
|
| 79 |
+
for mapping in self.maps:
|
| 80 |
+
mapping[key] = value
|
| 81 |
+
|
| 82 |
+
def __delitem__(self, key: KT) -> None:
|
| 83 |
+
hit = False
|
| 84 |
+
for mapping in self.maps:
|
| 85 |
+
if key in mapping:
|
| 86 |
+
del mapping[key]
|
| 87 |
+
hit = True
|
| 88 |
+
if not hit:
|
| 89 |
+
raise KeyError(key)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
# Despite the fact that LimitedDict _seems_ no longer necessary, I'm very nervous to actually remove it
|
| 93 |
+
# and discover later on that we need to re-add all this infrastructure...
|
| 94 |
+
# _GENERIC_TYPES_CACHE = DeepChainMap(GenericTypesCache(), LimitedDict())
|
| 95 |
+
|
| 96 |
+
_GENERIC_TYPES_CACHE = GenericTypesCache()
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class PydanticGenericMetadata(TypedDict):
|
| 100 |
+
origin: type[BaseModel] | None # analogous to typing._GenericAlias.__origin__
|
| 101 |
+
args: tuple[Any, ...] # analogous to typing._GenericAlias.__args__
|
| 102 |
+
parameters: tuple[TypeVar, ...] # analogous to typing.Generic.__parameters__
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def create_generic_submodel(
|
| 106 |
+
model_name: str, origin: type[BaseModel], args: tuple[Any, ...], params: tuple[Any, ...]
|
| 107 |
+
) -> type[BaseModel]:
|
| 108 |
+
"""Dynamically create a submodel of a provided (generic) BaseModel.
|
| 109 |
+
|
| 110 |
+
This is used when producing concrete parametrizations of generic models. This function
|
| 111 |
+
only *creates* the new subclass; the schema/validators/serialization must be updated to
|
| 112 |
+
reflect a concrete parametrization elsewhere.
|
| 113 |
+
|
| 114 |
+
Args:
|
| 115 |
+
model_name: The name of the newly created model.
|
| 116 |
+
origin: The base class for the new model to inherit from.
|
| 117 |
+
args: A tuple of generic metadata arguments.
|
| 118 |
+
params: A tuple of generic metadata parameters.
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
The created submodel.
|
| 122 |
+
"""
|
| 123 |
+
namespace: dict[str, Any] = {'__module__': origin.__module__}
|
| 124 |
+
bases = (origin,)
|
| 125 |
+
meta, ns, kwds = prepare_class(model_name, bases)
|
| 126 |
+
namespace.update(ns)
|
| 127 |
+
created_model = meta(
|
| 128 |
+
model_name,
|
| 129 |
+
bases,
|
| 130 |
+
namespace,
|
| 131 |
+
__pydantic_generic_metadata__={
|
| 132 |
+
'origin': origin,
|
| 133 |
+
'args': args,
|
| 134 |
+
'parameters': params,
|
| 135 |
+
},
|
| 136 |
+
__pydantic_reset_parent_namespace__=False,
|
| 137 |
+
**kwds,
|
| 138 |
+
)
|
| 139 |
+
|
| 140 |
+
model_module, called_globally = _get_caller_frame_info(depth=3)
|
| 141 |
+
if called_globally: # create global reference and therefore allow pickling
|
| 142 |
+
object_by_reference = None
|
| 143 |
+
reference_name = model_name
|
| 144 |
+
reference_module_globals = sys.modules[created_model.__module__].__dict__
|
| 145 |
+
while object_by_reference is not created_model:
|
| 146 |
+
object_by_reference = reference_module_globals.setdefault(reference_name, created_model)
|
| 147 |
+
reference_name += '_'
|
| 148 |
+
|
| 149 |
+
return created_model
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _get_caller_frame_info(depth: int = 2) -> tuple[str | None, bool]:
|
| 153 |
+
"""Used inside a function to check whether it was called globally.
|
| 154 |
+
|
| 155 |
+
Args:
|
| 156 |
+
depth: The depth to get the frame.
|
| 157 |
+
|
| 158 |
+
Returns:
|
| 159 |
+
A tuple contains `module_name` and `called_globally`.
|
| 160 |
+
|
| 161 |
+
Raises:
|
| 162 |
+
RuntimeError: If the function is not called inside a function.
|
| 163 |
+
"""
|
| 164 |
+
try:
|
| 165 |
+
previous_caller_frame = sys._getframe(depth)
|
| 166 |
+
except ValueError as e:
|
| 167 |
+
raise RuntimeError('This function must be used inside another function') from e
|
| 168 |
+
except AttributeError: # sys module does not have _getframe function, so there's nothing we can do about it
|
| 169 |
+
return None, False
|
| 170 |
+
frame_globals = previous_caller_frame.f_globals
|
| 171 |
+
return frame_globals.get('__name__'), previous_caller_frame.f_locals is frame_globals
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
DictValues: type[Any] = {}.values().__class__
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def iter_contained_typevars(v: Any) -> Iterator[TypeVar]:
|
| 178 |
+
"""Recursively iterate through all subtypes and type args of `v` and yield any typevars that are found.
|
| 179 |
+
|
| 180 |
+
This is inspired as an alternative to directly accessing the `__parameters__` attribute of a GenericAlias,
|
| 181 |
+
since __parameters__ of (nested) generic BaseModel subclasses won't show up in that list.
|
| 182 |
+
"""
|
| 183 |
+
if isinstance(v, TypeVar):
|
| 184 |
+
yield v
|
| 185 |
+
elif is_model_class(v):
|
| 186 |
+
yield from v.__pydantic_generic_metadata__['parameters']
|
| 187 |
+
elif isinstance(v, (DictValues, list)):
|
| 188 |
+
for var in v:
|
| 189 |
+
yield from iter_contained_typevars(var)
|
| 190 |
+
else:
|
| 191 |
+
args = get_args(v)
|
| 192 |
+
for arg in args:
|
| 193 |
+
yield from iter_contained_typevars(arg)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def get_args(v: Any) -> Any:
|
| 197 |
+
pydantic_generic_metadata: PydanticGenericMetadata | None = getattr(v, '__pydantic_generic_metadata__', None)
|
| 198 |
+
if pydantic_generic_metadata:
|
| 199 |
+
return pydantic_generic_metadata.get('args')
|
| 200 |
+
return typing_extensions.get_args(v)
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def get_origin(v: Any) -> Any:
|
| 204 |
+
pydantic_generic_metadata: PydanticGenericMetadata | None = getattr(v, '__pydantic_generic_metadata__', None)
|
| 205 |
+
if pydantic_generic_metadata:
|
| 206 |
+
return pydantic_generic_metadata.get('origin')
|
| 207 |
+
return typing_extensions.get_origin(v)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def get_standard_typevars_map(cls: Any) -> dict[TypeVar, Any] | None:
|
| 211 |
+
"""Package a generic type's typevars and parametrization (if present) into a dictionary compatible with the
|
| 212 |
+
`replace_types` function. Specifically, this works with standard typing generics and typing._GenericAlias.
|
| 213 |
+
"""
|
| 214 |
+
origin = get_origin(cls)
|
| 215 |
+
if origin is None:
|
| 216 |
+
return None
|
| 217 |
+
if not hasattr(origin, '__parameters__'):
|
| 218 |
+
return None
|
| 219 |
+
|
| 220 |
+
# In this case, we know that cls is a _GenericAlias, and origin is the generic type
|
| 221 |
+
# So it is safe to access cls.__args__ and origin.__parameters__
|
| 222 |
+
args: tuple[Any, ...] = cls.__args__ # type: ignore
|
| 223 |
+
parameters: tuple[TypeVar, ...] = origin.__parameters__
|
| 224 |
+
return dict(zip(parameters, args))
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def get_model_typevars_map(cls: type[BaseModel]) -> dict[TypeVar, Any]:
|
| 228 |
+
"""Package a generic BaseModel's typevars and concrete parametrization (if present) into a dictionary compatible
|
| 229 |
+
with the `replace_types` function.
|
| 230 |
+
|
| 231 |
+
Since BaseModel.__class_getitem__ does not produce a typing._GenericAlias, and the BaseModel generic info is
|
| 232 |
+
stored in the __pydantic_generic_metadata__ attribute, we need special handling here.
|
| 233 |
+
"""
|
| 234 |
+
# TODO: This could be unified with `get_standard_typevars_map` if we stored the generic metadata
|
| 235 |
+
# in the __origin__, __args__, and __parameters__ attributes of the model.
|
| 236 |
+
generic_metadata = cls.__pydantic_generic_metadata__
|
| 237 |
+
origin = generic_metadata['origin']
|
| 238 |
+
args = generic_metadata['args']
|
| 239 |
+
if not args:
|
| 240 |
+
# No need to go into `iter_contained_typevars`:
|
| 241 |
+
return {}
|
| 242 |
+
return dict(zip(iter_contained_typevars(origin), args))
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def replace_types(type_: Any, type_map: Mapping[TypeVar, Any] | None) -> Any:
|
| 246 |
+
"""Return type with all occurrences of `type_map` keys recursively replaced with their values.
|
| 247 |
+
|
| 248 |
+
Args:
|
| 249 |
+
type_: The class or generic alias.
|
| 250 |
+
type_map: Mapping from `TypeVar` instance to concrete types.
|
| 251 |
+
|
| 252 |
+
Returns:
|
| 253 |
+
A new type representing the basic structure of `type_` with all
|
| 254 |
+
`typevar_map` keys recursively replaced.
|
| 255 |
+
|
| 256 |
+
Example:
|
| 257 |
+
```python
|
| 258 |
+
from typing import Union
|
| 259 |
+
|
| 260 |
+
from pydantic._internal._generics import replace_types
|
| 261 |
+
|
| 262 |
+
replace_types(tuple[str, Union[list[str], float]], {str: int})
|
| 263 |
+
#> tuple[int, Union[list[int], float]]
|
| 264 |
+
```
|
| 265 |
+
"""
|
| 266 |
+
if not type_map:
|
| 267 |
+
return type_
|
| 268 |
+
|
| 269 |
+
type_args = get_args(type_)
|
| 270 |
+
origin_type = get_origin(type_)
|
| 271 |
+
|
| 272 |
+
if typing_objects.is_annotated(origin_type):
|
| 273 |
+
annotated_type, *annotations = type_args
|
| 274 |
+
annotated_type = replace_types(annotated_type, type_map)
|
| 275 |
+
# TODO remove parentheses when we drop support for Python 3.10:
|
| 276 |
+
return Annotated[(annotated_type, *annotations)]
|
| 277 |
+
|
| 278 |
+
# Having type args is a good indicator that this is a typing special form
|
| 279 |
+
# instance or a generic alias of some sort.
|
| 280 |
+
if type_args:
|
| 281 |
+
resolved_type_args = tuple(replace_types(arg, type_map) for arg in type_args)
|
| 282 |
+
if all_identical(type_args, resolved_type_args):
|
| 283 |
+
# If all arguments are the same, there is no need to modify the
|
| 284 |
+
# type or create a new object at all
|
| 285 |
+
return type_
|
| 286 |
+
|
| 287 |
+
if (
|
| 288 |
+
origin_type is not None
|
| 289 |
+
and isinstance(type_, _typing_extra.typing_base)
|
| 290 |
+
and not isinstance(origin_type, _typing_extra.typing_base)
|
| 291 |
+
and getattr(type_, '_name', None) is not None
|
| 292 |
+
):
|
| 293 |
+
# In python < 3.9 generic aliases don't exist so any of these like `list`,
|
| 294 |
+
# `type` or `collections.abc.Callable` need to be translated.
|
| 295 |
+
# See: https://www.python.org/dev/peps/pep-0585
|
| 296 |
+
origin_type = getattr(typing, type_._name)
|
| 297 |
+
assert origin_type is not None
|
| 298 |
+
|
| 299 |
+
if is_union_origin(origin_type):
|
| 300 |
+
if any(typing_objects.is_any(arg) for arg in resolved_type_args):
|
| 301 |
+
# `Any | T` ~ `Any`:
|
| 302 |
+
resolved_type_args = (Any,)
|
| 303 |
+
# `Never | T` ~ `T`:
|
| 304 |
+
resolved_type_args = tuple(
|
| 305 |
+
arg
|
| 306 |
+
for arg in resolved_type_args
|
| 307 |
+
if not (typing_objects.is_noreturn(arg) or typing_objects.is_never(arg))
|
| 308 |
+
)
|
| 309 |
+
|
| 310 |
+
# PEP-604 syntax (e.g. `list | str`) is represented with a types.UnionType object that does not
|
| 311 |
+
# implement `__getitem__()`. In Python 3.14+, `typing.Union` and `types.UnionType` are the same,
|
| 312 |
+
# and we instead rely on `typing.Union` as it implicitly converts string annotations to `ForwardRef`
|
| 313 |
+
# instances (this is to avoid type errors as per https://github.com/python/cpython/pull/105366).
|
| 314 |
+
# TODO remove type ignore comment when we drop support for Python 3.9 (https://github.com/microsoft/pyright/issues/11241):
|
| 315 |
+
if (3, 10) <= sys.version_info < (3, 14) and origin_type is types.UnionType: # pyright: ignore[reportAttributeAccessIssue]
|
| 316 |
+
return reduce(operator.or_, resolved_type_args)
|
| 317 |
+
# NotRequired[T] and Required[T] don't support tuple type resolved_type_args, hence the condition below
|
| 318 |
+
return origin_type[resolved_type_args[0] if len(resolved_type_args) == 1 else resolved_type_args]
|
| 319 |
+
|
| 320 |
+
# We handle pydantic generic models separately as they don't have the same
|
| 321 |
+
# semantics as "typing" classes or generic aliases
|
| 322 |
+
|
| 323 |
+
if not origin_type and is_model_class(type_):
|
| 324 |
+
parameters = type_.__pydantic_generic_metadata__['parameters']
|
| 325 |
+
if not parameters:
|
| 326 |
+
return type_
|
| 327 |
+
resolved_type_args = tuple(replace_types(t, type_map) for t in parameters)
|
| 328 |
+
if all_identical(parameters, resolved_type_args):
|
| 329 |
+
return type_
|
| 330 |
+
return type_[resolved_type_args]
|
| 331 |
+
|
| 332 |
+
# Handle special case for typehints that can have lists as arguments.
|
| 333 |
+
# `typing.Callable[[int, str], int]` is an example for this.
|
| 334 |
+
if isinstance(type_, list):
|
| 335 |
+
resolved_list = [replace_types(element, type_map) for element in type_]
|
| 336 |
+
if all_identical(type_, resolved_list):
|
| 337 |
+
return type_
|
| 338 |
+
return resolved_list
|
| 339 |
+
|
| 340 |
+
# If all else fails, we try to resolve the type directly and otherwise just
|
| 341 |
+
# return the input with no modifications.
|
| 342 |
+
return type_map.get(type_, type_)
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
def map_generic_model_arguments(cls: type[BaseModel], args: tuple[Any, ...]) -> dict[TypeVar, Any]:
|
| 346 |
+
"""Return a mapping between the parameters of a generic model and the provided arguments during parameterization.
|
| 347 |
+
|
| 348 |
+
Raises:
|
| 349 |
+
TypeError: If the number of arguments does not match the parameters (i.e. if providing too few or too many arguments).
|
| 350 |
+
|
| 351 |
+
Example:
|
| 352 |
+
```python {test="skip" lint="skip"}
|
| 353 |
+
class Model[T, U, V = int](BaseModel): ...
|
| 354 |
+
|
| 355 |
+
map_generic_model_arguments(Model, (str, bytes))
|
| 356 |
+
#> {T: str, U: bytes, V: int}
|
| 357 |
+
|
| 358 |
+
map_generic_model_arguments(Model, (str,))
|
| 359 |
+
#> TypeError: Too few arguments for <class '__main__.Model'>; actual 1, expected at least 2
|
| 360 |
+
|
| 361 |
+
map_generic_model_arguments(Model, (str, bytes, int, complex))
|
| 362 |
+
#> TypeError: Too many arguments for <class '__main__.Model'>; actual 4, expected 3
|
| 363 |
+
```
|
| 364 |
+
|
| 365 |
+
Note:
|
| 366 |
+
This function is analogous to the private `typing._check_generic_specialization` function.
|
| 367 |
+
"""
|
| 368 |
+
parameters = cls.__pydantic_generic_metadata__['parameters']
|
| 369 |
+
expected_len = len(parameters)
|
| 370 |
+
typevars_map: dict[TypeVar, Any] = {}
|
| 371 |
+
|
| 372 |
+
_missing = object()
|
| 373 |
+
for parameter, argument in zip_longest(parameters, args, fillvalue=_missing):
|
| 374 |
+
if parameter is _missing:
|
| 375 |
+
raise TypeError(f'Too many arguments for {cls}; actual {len(args)}, expected {expected_len}')
|
| 376 |
+
|
| 377 |
+
if argument is _missing:
|
| 378 |
+
param = cast(TypeVar, parameter)
|
| 379 |
+
try:
|
| 380 |
+
has_default = param.has_default() # pyright: ignore[reportAttributeAccessIssue]
|
| 381 |
+
except AttributeError:
|
| 382 |
+
# Happens if using `typing.TypeVar` (and not `typing_extensions`) on Python < 3.13.
|
| 383 |
+
has_default = False
|
| 384 |
+
if has_default:
|
| 385 |
+
# The default might refer to other type parameters. For an example, see:
|
| 386 |
+
# https://typing.python.org/en/latest/spec/generics.html#type-parameters-as-parameters-to-generics
|
| 387 |
+
typevars_map[param] = replace_types(param.__default__, typevars_map) # pyright: ignore[reportAttributeAccessIssue]
|
| 388 |
+
else:
|
| 389 |
+
expected_len -= sum(hasattr(p, 'has_default') and p.has_default() for p in parameters) # pyright: ignore[reportAttributeAccessIssue]
|
| 390 |
+
raise TypeError(f'Too few arguments for {cls}; actual {len(args)}, expected at least {expected_len}')
|
| 391 |
+
else:
|
| 392 |
+
param = cast(TypeVar, parameter)
|
| 393 |
+
typevars_map[param] = argument
|
| 394 |
+
|
| 395 |
+
return typevars_map
|
| 396 |
+
|
| 397 |
+
|
| 398 |
+
_generic_recursion_cache: ContextVar[set[str] | None] = ContextVar('_generic_recursion_cache', default=None)
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
@contextmanager
|
| 402 |
+
def generic_recursion_self_type(
|
| 403 |
+
origin: type[BaseModel], args: tuple[Any, ...]
|
| 404 |
+
) -> Iterator[PydanticRecursiveRef | None]:
|
| 405 |
+
"""This contextmanager should be placed around the recursive calls used to build a generic type,
|
| 406 |
+
and accept as arguments the generic origin type and the type arguments being passed to it.
|
| 407 |
+
|
| 408 |
+
If the same origin and arguments are observed twice, it implies that a self-reference placeholder
|
| 409 |
+
can be used while building the core schema, and will produce a schema_ref that will be valid in the
|
| 410 |
+
final parent schema.
|
| 411 |
+
"""
|
| 412 |
+
previously_seen_type_refs = _generic_recursion_cache.get()
|
| 413 |
+
if previously_seen_type_refs is None:
|
| 414 |
+
previously_seen_type_refs = set()
|
| 415 |
+
token = _generic_recursion_cache.set(previously_seen_type_refs)
|
| 416 |
+
else:
|
| 417 |
+
token = None
|
| 418 |
+
|
| 419 |
+
try:
|
| 420 |
+
type_ref = get_type_ref(origin, args_override=args)
|
| 421 |
+
if type_ref in previously_seen_type_refs:
|
| 422 |
+
self_type = PydanticRecursiveRef(type_ref=type_ref)
|
| 423 |
+
yield self_type
|
| 424 |
+
else:
|
| 425 |
+
previously_seen_type_refs.add(type_ref)
|
| 426 |
+
yield
|
| 427 |
+
previously_seen_type_refs.remove(type_ref)
|
| 428 |
+
finally:
|
| 429 |
+
if token:
|
| 430 |
+
_generic_recursion_cache.reset(token)
|
| 431 |
+
|
| 432 |
+
|
| 433 |
+
def recursively_defined_type_refs() -> set[str]:
|
| 434 |
+
visited = _generic_recursion_cache.get()
|
| 435 |
+
if not visited:
|
| 436 |
+
return set() # not in a generic recursion, so there are no types
|
| 437 |
+
|
| 438 |
+
return visited.copy() # don't allow modifications
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
def get_cached_generic_type_early(parent: type[BaseModel], typevar_values: Any) -> type[BaseModel] | None:
|
| 442 |
+
"""The use of a two-stage cache lookup approach was necessary to have the highest performance possible for
|
| 443 |
+
repeated calls to `__class_getitem__` on generic types (which may happen in tighter loops during runtime),
|
| 444 |
+
while still ensuring that certain alternative parametrizations ultimately resolve to the same type.
|
| 445 |
+
|
| 446 |
+
As a concrete example, this approach was necessary to make Model[List[T]][int] equal to Model[List[int]].
|
| 447 |
+
The approach could be modified to not use two different cache keys at different points, but the
|
| 448 |
+
_early_cache_key is optimized to be as quick to compute as possible (for repeated-access speed), and the
|
| 449 |
+
_late_cache_key is optimized to be as "correct" as possible, so that two types that will ultimately be the
|
| 450 |
+
same after resolving the type arguments will always produce cache hits.
|
| 451 |
+
|
| 452 |
+
If we wanted to move to only using a single cache key per type, we would either need to always use the
|
| 453 |
+
slower/more computationally intensive logic associated with _late_cache_key, or would need to accept
|
| 454 |
+
that Model[List[T]][int] is a different type than Model[List[T]][int]. Because we rely on subclass relationships
|
| 455 |
+
during validation, I think it is worthwhile to ensure that types that are functionally equivalent are actually
|
| 456 |
+
equal.
|
| 457 |
+
"""
|
| 458 |
+
return _GENERIC_TYPES_CACHE.get(_early_cache_key(parent, typevar_values))
|
| 459 |
+
|
| 460 |
+
|
| 461 |
+
def get_cached_generic_type_late(
|
| 462 |
+
parent: type[BaseModel], typevar_values: Any, origin: type[BaseModel], args: tuple[Any, ...]
|
| 463 |
+
) -> type[BaseModel] | None:
|
| 464 |
+
"""See the docstring of `get_cached_generic_type_early` for more information about the two-stage cache lookup."""
|
| 465 |
+
cached = _GENERIC_TYPES_CACHE.get(_late_cache_key(origin, args, typevar_values))
|
| 466 |
+
if cached is not None:
|
| 467 |
+
set_cached_generic_type(parent, typevar_values, cached, origin, args)
|
| 468 |
+
return cached
|
| 469 |
+
|
| 470 |
+
|
| 471 |
+
def set_cached_generic_type(
|
| 472 |
+
parent: type[BaseModel],
|
| 473 |
+
typevar_values: tuple[Any, ...],
|
| 474 |
+
type_: type[BaseModel],
|
| 475 |
+
origin: type[BaseModel] | None = None,
|
| 476 |
+
args: tuple[Any, ...] | None = None,
|
| 477 |
+
) -> None:
|
| 478 |
+
"""See the docstring of `get_cached_generic_type_early` for more information about why items are cached with
|
| 479 |
+
two different keys.
|
| 480 |
+
"""
|
| 481 |
+
_GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values)] = type_
|
| 482 |
+
if len(typevar_values) == 1:
|
| 483 |
+
_GENERIC_TYPES_CACHE[_early_cache_key(parent, typevar_values[0])] = type_
|
| 484 |
+
if origin and args:
|
| 485 |
+
_GENERIC_TYPES_CACHE[_late_cache_key(origin, args, typevar_values)] = type_
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def _union_orderings_key(typevar_values: Any) -> Any:
|
| 489 |
+
"""This is intended to help differentiate between Union types with the same arguments in different order.
|
| 490 |
+
|
| 491 |
+
Thanks to caching internal to the `typing` module, it is not possible to distinguish between
|
| 492 |
+
List[Union[int, float]] and List[Union[float, int]] (and similarly for other "parent" origins besides List)
|
| 493 |
+
because `typing` considers Union[int, float] to be equal to Union[float, int].
|
| 494 |
+
|
| 495 |
+
However, you _can_ distinguish between (top-level) Union[int, float] vs. Union[float, int].
|
| 496 |
+
Because we parse items as the first Union type that is successful, we get slightly more consistent behavior
|
| 497 |
+
if we make an effort to distinguish the ordering of items in a union. It would be best if we could _always_
|
| 498 |
+
get the exact-correct order of items in the union, but that would require a change to the `typing` module itself.
|
| 499 |
+
(See https://github.com/python/cpython/issues/86483 for reference.)
|
| 500 |
+
"""
|
| 501 |
+
if isinstance(typevar_values, tuple):
|
| 502 |
+
return tuple(_union_orderings_key(value) for value in typevar_values)
|
| 503 |
+
elif typing_objects.is_union(typing_extensions.get_origin(typevar_values)):
|
| 504 |
+
return get_args(typevar_values)
|
| 505 |
+
else:
|
| 506 |
+
return ()
|
| 507 |
+
|
| 508 |
+
|
| 509 |
+
def _early_cache_key(cls: type[BaseModel], typevar_values: Any) -> GenericTypesCacheKey:
|
| 510 |
+
"""This is intended for minimal computational overhead during lookups of cached types.
|
| 511 |
+
|
| 512 |
+
Note that this is overly simplistic, and it's possible that two different cls/typevar_values
|
| 513 |
+
inputs would ultimately result in the same type being created in BaseModel.__class_getitem__.
|
| 514 |
+
To handle this, we have a fallback _late_cache_key that is checked later if the _early_cache_key
|
| 515 |
+
lookup fails, and should result in a cache hit _precisely_ when the inputs to __class_getitem__
|
| 516 |
+
would result in the same type.
|
| 517 |
+
"""
|
| 518 |
+
return cls, typevar_values, _union_orderings_key(typevar_values)
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def _late_cache_key(origin: type[BaseModel], args: tuple[Any, ...], typevar_values: Any) -> GenericTypesCacheKey:
|
| 522 |
+
"""This is intended for use later in the process of creating a new type, when we have more information
|
| 523 |
+
about the exact args that will be passed. If it turns out that a different set of inputs to
|
| 524 |
+
__class_getitem__ resulted in the same inputs to the generic type creation process, we can still
|
| 525 |
+
return the cached type, and update the cache with the _early_cache_key as well.
|
| 526 |
+
"""
|
| 527 |
+
# The _union_orderings_key is placed at the start here to ensure there cannot be a collision with an
|
| 528 |
+
# _early_cache_key, as that function will always produce a BaseModel subclass as the first item in the key,
|
| 529 |
+
# whereas this function will always produce a tuple as the first item in the key.
|
| 530 |
+
return _union_orderings_key(typevar_values), origin, args
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_git.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Git utilities, adopted from mypy's git utilities (https://github.com/python/mypy/blob/master/mypy/git.py)."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import subprocess
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def is_git_repo(dir: Path) -> bool:
|
| 10 |
+
"""Is the given directory version-controlled with git?"""
|
| 11 |
+
return dir.joinpath('.git').exists()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def have_git() -> bool: # pragma: no cover
|
| 15 |
+
"""Can we run the git executable?"""
|
| 16 |
+
try:
|
| 17 |
+
subprocess.check_output(['git', '--help'])
|
| 18 |
+
return True
|
| 19 |
+
except subprocess.CalledProcessError:
|
| 20 |
+
return False
|
| 21 |
+
except OSError:
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def git_revision(dir: Path) -> str:
|
| 26 |
+
"""Get the SHA-1 of the HEAD of a git repository."""
|
| 27 |
+
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], cwd=dir).decode('utf-8').strip()
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_import_utils.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from functools import cache
|
| 2 |
+
from typing import TYPE_CHECKING
|
| 3 |
+
|
| 4 |
+
if TYPE_CHECKING:
|
| 5 |
+
from pydantic import BaseModel
|
| 6 |
+
from pydantic.fields import FieldInfo
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@cache
|
| 10 |
+
def import_cached_base_model() -> type['BaseModel']:
|
| 11 |
+
from pydantic import BaseModel
|
| 12 |
+
|
| 13 |
+
return BaseModel
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
@cache
|
| 17 |
+
def import_cached_field_info() -> type['FieldInfo']:
|
| 18 |
+
from pydantic.fields import FieldInfo
|
| 19 |
+
|
| 20 |
+
return FieldInfo
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_internal_dataclass.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
|
| 3 |
+
# `slots` is available on Python >= 3.10
|
| 4 |
+
if sys.version_info >= (3, 10):
|
| 5 |
+
slots_true = {'slots': True}
|
| 6 |
+
else:
|
| 7 |
+
slots_true = {}
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_known_annotated_metadata.py
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
from collections.abc import Iterable
|
| 5 |
+
from copy import copy
|
| 6 |
+
from functools import lru_cache, partial
|
| 7 |
+
from typing import TYPE_CHECKING, Any
|
| 8 |
+
|
| 9 |
+
from pydantic_core import CoreSchema, PydanticCustomError, ValidationError, to_jsonable_python
|
| 10 |
+
from pydantic_core import core_schema as cs
|
| 11 |
+
|
| 12 |
+
from ._fields import PydanticMetadata
|
| 13 |
+
from ._import_utils import import_cached_field_info
|
| 14 |
+
|
| 15 |
+
if TYPE_CHECKING:
|
| 16 |
+
pass
|
| 17 |
+
|
| 18 |
+
STRICT = {'strict'}
|
| 19 |
+
FAIL_FAST = {'fail_fast'}
|
| 20 |
+
LENGTH_CONSTRAINTS = {'min_length', 'max_length'}
|
| 21 |
+
INEQUALITY = {'le', 'ge', 'lt', 'gt'}
|
| 22 |
+
NUMERIC_CONSTRAINTS = {'multiple_of', *INEQUALITY}
|
| 23 |
+
ALLOW_INF_NAN = {'allow_inf_nan'}
|
| 24 |
+
|
| 25 |
+
STR_CONSTRAINTS = {
|
| 26 |
+
*LENGTH_CONSTRAINTS,
|
| 27 |
+
*STRICT,
|
| 28 |
+
'strip_whitespace',
|
| 29 |
+
'to_lower',
|
| 30 |
+
'to_upper',
|
| 31 |
+
'pattern',
|
| 32 |
+
'coerce_numbers_to_str',
|
| 33 |
+
'ascii_only',
|
| 34 |
+
}
|
| 35 |
+
BYTES_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT}
|
| 36 |
+
|
| 37 |
+
LIST_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST}
|
| 38 |
+
TUPLE_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST}
|
| 39 |
+
SET_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT, *FAIL_FAST}
|
| 40 |
+
DICT_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT}
|
| 41 |
+
GENERATOR_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *STRICT}
|
| 42 |
+
SEQUENCE_CONSTRAINTS = {*LENGTH_CONSTRAINTS, *FAIL_FAST}
|
| 43 |
+
|
| 44 |
+
FLOAT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *ALLOW_INF_NAN, *STRICT}
|
| 45 |
+
DECIMAL_CONSTRAINTS = {'max_digits', 'decimal_places', *FLOAT_CONSTRAINTS}
|
| 46 |
+
INT_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *ALLOW_INF_NAN, *STRICT}
|
| 47 |
+
BOOL_CONSTRAINTS = STRICT
|
| 48 |
+
UUID_CONSTRAINTS = STRICT
|
| 49 |
+
|
| 50 |
+
DATE_TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
|
| 51 |
+
TIMEDELTA_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
|
| 52 |
+
TIME_CONSTRAINTS = {*NUMERIC_CONSTRAINTS, *STRICT}
|
| 53 |
+
LAX_OR_STRICT_CONSTRAINTS = STRICT
|
| 54 |
+
ENUM_CONSTRAINTS = STRICT
|
| 55 |
+
COMPLEX_CONSTRAINTS = STRICT
|
| 56 |
+
|
| 57 |
+
UNION_CONSTRAINTS = {'union_mode'}
|
| 58 |
+
URL_CONSTRAINTS = {
|
| 59 |
+
'max_length',
|
| 60 |
+
'allowed_schemes',
|
| 61 |
+
'host_required',
|
| 62 |
+
'default_host',
|
| 63 |
+
'default_port',
|
| 64 |
+
'default_path',
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
TEXT_SCHEMA_TYPES = ('str', 'bytes', 'url', 'multi-host-url')
|
| 68 |
+
SEQUENCE_SCHEMA_TYPES = ('list', 'tuple', 'set', 'frozenset', 'generator', *TEXT_SCHEMA_TYPES)
|
| 69 |
+
NUMERIC_SCHEMA_TYPES = ('float', 'int', 'date', 'time', 'timedelta', 'datetime')
|
| 70 |
+
|
| 71 |
+
CONSTRAINTS_TO_ALLOWED_SCHEMAS: dict[str, set[str]] = defaultdict(set)
|
| 72 |
+
|
| 73 |
+
constraint_schema_pairings: list[tuple[set[str], tuple[str, ...]]] = [
|
| 74 |
+
(STR_CONSTRAINTS, TEXT_SCHEMA_TYPES),
|
| 75 |
+
(BYTES_CONSTRAINTS, ('bytes',)),
|
| 76 |
+
(LIST_CONSTRAINTS, ('list',)),
|
| 77 |
+
(TUPLE_CONSTRAINTS, ('tuple',)),
|
| 78 |
+
(SET_CONSTRAINTS, ('set', 'frozenset')),
|
| 79 |
+
(DICT_CONSTRAINTS, ('dict',)),
|
| 80 |
+
(GENERATOR_CONSTRAINTS, ('generator',)),
|
| 81 |
+
(FLOAT_CONSTRAINTS, ('float',)),
|
| 82 |
+
(INT_CONSTRAINTS, ('int',)),
|
| 83 |
+
(DATE_TIME_CONSTRAINTS, ('date', 'time', 'datetime', 'timedelta')),
|
| 84 |
+
# TODO: this is a bit redundant, we could probably avoid some of these
|
| 85 |
+
(STRICT, (*TEXT_SCHEMA_TYPES, *SEQUENCE_SCHEMA_TYPES, *NUMERIC_SCHEMA_TYPES, 'typed-dict', 'model')),
|
| 86 |
+
(UNION_CONSTRAINTS, ('union',)),
|
| 87 |
+
(URL_CONSTRAINTS, ('url', 'multi-host-url')),
|
| 88 |
+
(BOOL_CONSTRAINTS, ('bool',)),
|
| 89 |
+
(UUID_CONSTRAINTS, ('uuid',)),
|
| 90 |
+
(LAX_OR_STRICT_CONSTRAINTS, ('lax-or-strict',)),
|
| 91 |
+
(ENUM_CONSTRAINTS, ('enum',)),
|
| 92 |
+
(DECIMAL_CONSTRAINTS, ('decimal',)),
|
| 93 |
+
(COMPLEX_CONSTRAINTS, ('complex',)),
|
| 94 |
+
]
|
| 95 |
+
|
| 96 |
+
for constraints, schemas in constraint_schema_pairings:
|
| 97 |
+
for c in constraints:
|
| 98 |
+
CONSTRAINTS_TO_ALLOWED_SCHEMAS[c].update(schemas)
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def as_jsonable_value(v: Any) -> Any:
|
| 102 |
+
if type(v) not in (int, str, float, bytes, bool, type(None)):
|
| 103 |
+
return to_jsonable_python(v)
|
| 104 |
+
return v
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def expand_grouped_metadata(annotations: Iterable[Any]) -> Iterable[Any]:
|
| 108 |
+
"""Expand the annotations.
|
| 109 |
+
|
| 110 |
+
Args:
|
| 111 |
+
annotations: An iterable of annotations.
|
| 112 |
+
|
| 113 |
+
Returns:
|
| 114 |
+
An iterable of expanded annotations.
|
| 115 |
+
|
| 116 |
+
Example:
|
| 117 |
+
```python
|
| 118 |
+
from annotated_types import Ge, Len
|
| 119 |
+
|
| 120 |
+
from pydantic._internal._known_annotated_metadata import expand_grouped_metadata
|
| 121 |
+
|
| 122 |
+
print(list(expand_grouped_metadata([Ge(4), Len(5)])))
|
| 123 |
+
#> [Ge(ge=4), MinLen(min_length=5)]
|
| 124 |
+
```
|
| 125 |
+
"""
|
| 126 |
+
import annotated_types as at
|
| 127 |
+
|
| 128 |
+
FieldInfo = import_cached_field_info()
|
| 129 |
+
|
| 130 |
+
for annotation in annotations:
|
| 131 |
+
if isinstance(annotation, at.GroupedMetadata):
|
| 132 |
+
yield from annotation
|
| 133 |
+
elif isinstance(annotation, FieldInfo):
|
| 134 |
+
yield from annotation.metadata
|
| 135 |
+
# this is a bit problematic in that it results in duplicate metadata
|
| 136 |
+
# all of our "consumers" can handle it, but it is not ideal
|
| 137 |
+
# we probably should split up FieldInfo into:
|
| 138 |
+
# - annotated types metadata
|
| 139 |
+
# - individual metadata known only to Pydantic
|
| 140 |
+
annotation = copy(annotation)
|
| 141 |
+
annotation.metadata = []
|
| 142 |
+
yield annotation
|
| 143 |
+
else:
|
| 144 |
+
yield annotation
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
@lru_cache
|
| 148 |
+
def _get_at_to_constraint_map() -> dict[type, str]:
|
| 149 |
+
"""Return a mapping of annotated types to constraints.
|
| 150 |
+
|
| 151 |
+
Normally, we would define a mapping like this in the module scope, but we can't do that
|
| 152 |
+
because we don't permit module level imports of `annotated_types`, in an attempt to speed up
|
| 153 |
+
the import time of `pydantic`. We still only want to have this dictionary defined in one place,
|
| 154 |
+
so we use this function to cache the result.
|
| 155 |
+
"""
|
| 156 |
+
import annotated_types as at
|
| 157 |
+
|
| 158 |
+
return {
|
| 159 |
+
at.Gt: 'gt',
|
| 160 |
+
at.Ge: 'ge',
|
| 161 |
+
at.Lt: 'lt',
|
| 162 |
+
at.Le: 'le',
|
| 163 |
+
at.MultipleOf: 'multiple_of',
|
| 164 |
+
at.MinLen: 'min_length',
|
| 165 |
+
at.MaxLen: 'max_length',
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
def apply_known_metadata(annotation: Any, schema: CoreSchema) -> CoreSchema | None: # noqa: C901
|
| 170 |
+
"""Apply `annotation` to `schema` if it is an annotation we know about (Gt, Le, etc.).
|
| 171 |
+
Otherwise return `None`.
|
| 172 |
+
|
| 173 |
+
This does not handle all known annotations. If / when it does, it can always
|
| 174 |
+
return a CoreSchema and return the unmodified schema if the annotation should be ignored.
|
| 175 |
+
|
| 176 |
+
Assumes that GroupedMetadata has already been expanded via `expand_grouped_metadata`.
|
| 177 |
+
|
| 178 |
+
Args:
|
| 179 |
+
annotation: The annotation.
|
| 180 |
+
schema: The schema.
|
| 181 |
+
|
| 182 |
+
Returns:
|
| 183 |
+
An updated schema with annotation if it is an annotation we know about, `None` otherwise.
|
| 184 |
+
|
| 185 |
+
Raises:
|
| 186 |
+
RuntimeError: If a constraint can't be applied to a specific schema type.
|
| 187 |
+
ValueError: If an unknown constraint is encountered.
|
| 188 |
+
"""
|
| 189 |
+
import annotated_types as at
|
| 190 |
+
|
| 191 |
+
from ._validators import NUMERIC_VALIDATOR_LOOKUP, forbid_inf_nan_check
|
| 192 |
+
|
| 193 |
+
schema = schema.copy()
|
| 194 |
+
schema_update, other_metadata = collect_known_metadata([annotation])
|
| 195 |
+
schema_type = schema['type']
|
| 196 |
+
|
| 197 |
+
chain_schema_constraints: set[str] = {
|
| 198 |
+
'pattern',
|
| 199 |
+
'strip_whitespace',
|
| 200 |
+
'to_lower',
|
| 201 |
+
'to_upper',
|
| 202 |
+
'coerce_numbers_to_str',
|
| 203 |
+
'ascii_only',
|
| 204 |
+
}
|
| 205 |
+
chain_schema_steps: list[CoreSchema] = []
|
| 206 |
+
|
| 207 |
+
for constraint, value in schema_update.items():
|
| 208 |
+
if constraint not in CONSTRAINTS_TO_ALLOWED_SCHEMAS:
|
| 209 |
+
raise ValueError(f'Unknown constraint {constraint}')
|
| 210 |
+
allowed_schemas = CONSTRAINTS_TO_ALLOWED_SCHEMAS[constraint]
|
| 211 |
+
|
| 212 |
+
# if it becomes necessary to handle more than one constraint
|
| 213 |
+
# in this recursive case with function-after or function-wrap, we should refactor
|
| 214 |
+
# this is a bit challenging because we sometimes want to apply constraints to the inner schema,
|
| 215 |
+
# whereas other times we want to wrap the existing schema with a new one that enforces a new constraint.
|
| 216 |
+
if schema_type in {'function-before', 'function-wrap', 'function-after'} and constraint == 'strict':
|
| 217 |
+
schema['schema'] = apply_known_metadata(annotation, schema['schema']) # type: ignore # schema is function schema
|
| 218 |
+
return schema
|
| 219 |
+
|
| 220 |
+
# if we're allowed to apply constraint directly to the schema, like le to int, do that
|
| 221 |
+
if schema_type in allowed_schemas:
|
| 222 |
+
if constraint == 'union_mode' and schema_type == 'union':
|
| 223 |
+
schema['mode'] = value # type: ignore # schema is UnionSchema
|
| 224 |
+
else:
|
| 225 |
+
schema[constraint] = value
|
| 226 |
+
continue
|
| 227 |
+
|
| 228 |
+
# else, apply a function after validator to the schema to enforce the corresponding constraint
|
| 229 |
+
if constraint in chain_schema_constraints:
|
| 230 |
+
|
| 231 |
+
def _apply_constraint_with_incompatibility_info(
|
| 232 |
+
value: Any, handler: cs.ValidatorFunctionWrapHandler
|
| 233 |
+
) -> Any:
|
| 234 |
+
try:
|
| 235 |
+
x = handler(value)
|
| 236 |
+
except ValidationError as ve:
|
| 237 |
+
# if the error is about the type, it's likely that the constraint is incompatible the type of the field
|
| 238 |
+
# for example, the following invalid schema wouldn't be caught during schema build, but rather at this point
|
| 239 |
+
# with a cryptic 'string_type' error coming from the string validator,
|
| 240 |
+
# that we'd rather express as a constraint incompatibility error (TypeError)
|
| 241 |
+
# Annotated[list[int], Field(pattern='abc')]
|
| 242 |
+
if 'type' in ve.errors()[0]['type']:
|
| 243 |
+
raise TypeError(
|
| 244 |
+
f"Unable to apply constraint '{constraint}' to supplied value {value} for schema of type '{schema_type}'" # noqa: B023
|
| 245 |
+
)
|
| 246 |
+
raise ve
|
| 247 |
+
return x
|
| 248 |
+
|
| 249 |
+
chain_schema_steps.append(
|
| 250 |
+
cs.no_info_wrap_validator_function(
|
| 251 |
+
_apply_constraint_with_incompatibility_info, cs.str_schema(**{constraint: value})
|
| 252 |
+
)
|
| 253 |
+
)
|
| 254 |
+
elif constraint in NUMERIC_VALIDATOR_LOOKUP:
|
| 255 |
+
if constraint in LENGTH_CONSTRAINTS:
|
| 256 |
+
inner_schema = schema
|
| 257 |
+
while inner_schema['type'] in {'function-before', 'function-wrap', 'function-after'}:
|
| 258 |
+
inner_schema = inner_schema['schema'] # type: ignore
|
| 259 |
+
inner_schema_type = inner_schema['type']
|
| 260 |
+
if inner_schema_type == 'list' or (
|
| 261 |
+
inner_schema_type == 'json-or-python' and inner_schema['json_schema']['type'] == 'list' # type: ignore
|
| 262 |
+
):
|
| 263 |
+
js_constraint_key = 'minItems' if constraint == 'min_length' else 'maxItems'
|
| 264 |
+
else:
|
| 265 |
+
js_constraint_key = 'minLength' if constraint == 'min_length' else 'maxLength'
|
| 266 |
+
else:
|
| 267 |
+
js_constraint_key = constraint
|
| 268 |
+
|
| 269 |
+
schema = cs.no_info_after_validator_function(
|
| 270 |
+
partial(NUMERIC_VALIDATOR_LOOKUP[constraint], **{constraint: value}), schema
|
| 271 |
+
)
|
| 272 |
+
metadata = schema.get('metadata', {})
|
| 273 |
+
if (existing_json_schema_updates := metadata.get('pydantic_js_updates')) is not None:
|
| 274 |
+
metadata['pydantic_js_updates'] = {
|
| 275 |
+
**existing_json_schema_updates,
|
| 276 |
+
**{js_constraint_key: as_jsonable_value(value)},
|
| 277 |
+
}
|
| 278 |
+
else:
|
| 279 |
+
metadata['pydantic_js_updates'] = {js_constraint_key: as_jsonable_value(value)}
|
| 280 |
+
schema['metadata'] = metadata
|
| 281 |
+
elif constraint == 'allow_inf_nan' and value is False:
|
| 282 |
+
schema = cs.no_info_after_validator_function(
|
| 283 |
+
forbid_inf_nan_check,
|
| 284 |
+
schema,
|
| 285 |
+
)
|
| 286 |
+
else:
|
| 287 |
+
# It's rare that we'd get here, but it's possible if we add a new constraint and forget to handle it
|
| 288 |
+
# Most constraint errors are caught at runtime during attempted application
|
| 289 |
+
raise RuntimeError(f"Unable to apply constraint '{constraint}' to schema of type '{schema_type}'")
|
| 290 |
+
|
| 291 |
+
for annotation in other_metadata:
|
| 292 |
+
if (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()):
|
| 293 |
+
constraint = at_to_constraint_map[annotation_type]
|
| 294 |
+
validator = NUMERIC_VALIDATOR_LOOKUP.get(constraint)
|
| 295 |
+
if validator is None:
|
| 296 |
+
raise ValueError(f'Unknown constraint {constraint}')
|
| 297 |
+
schema = cs.no_info_after_validator_function(
|
| 298 |
+
partial(validator, {constraint: getattr(annotation, constraint)}), schema
|
| 299 |
+
)
|
| 300 |
+
continue
|
| 301 |
+
elif isinstance(annotation, (at.Predicate, at.Not)):
|
| 302 |
+
predicate_name = f'{annotation.func.__qualname__!r} ' if hasattr(annotation.func, '__qualname__') else ''
|
| 303 |
+
|
| 304 |
+
# Note: B023 is ignored because even though we iterate over `other_metadata`, it is guaranteed
|
| 305 |
+
# to be of length 1. `apply_known_metadata()` is called from `GenerateSchema`, where annotations
|
| 306 |
+
# were already expanded via `expand_grouped_metadata()`. Confusing, but this falls into the annotations
|
| 307 |
+
# refactor.
|
| 308 |
+
if isinstance(annotation, at.Predicate):
|
| 309 |
+
|
| 310 |
+
def val_func(v: Any) -> Any:
|
| 311 |
+
predicate_satisfied = annotation.func(v) # noqa: B023
|
| 312 |
+
if not predicate_satisfied:
|
| 313 |
+
raise PydanticCustomError(
|
| 314 |
+
'predicate_failed',
|
| 315 |
+
f'Predicate {predicate_name}failed', # pyright: ignore[reportArgumentType] # noqa: B023
|
| 316 |
+
)
|
| 317 |
+
return v
|
| 318 |
+
|
| 319 |
+
else:
|
| 320 |
+
|
| 321 |
+
def val_func(v: Any) -> Any:
|
| 322 |
+
predicate_satisfied = annotation.func(v) # noqa: B023
|
| 323 |
+
if predicate_satisfied:
|
| 324 |
+
raise PydanticCustomError(
|
| 325 |
+
'not_operation_failed',
|
| 326 |
+
f'Not of {predicate_name}failed', # pyright: ignore[reportArgumentType] # noqa: B023
|
| 327 |
+
)
|
| 328 |
+
return v
|
| 329 |
+
|
| 330 |
+
schema = cs.no_info_after_validator_function(val_func, schema)
|
| 331 |
+
else:
|
| 332 |
+
# ignore any other unknown metadata
|
| 333 |
+
return None
|
| 334 |
+
|
| 335 |
+
if chain_schema_steps:
|
| 336 |
+
chain_schema_steps = [schema] + chain_schema_steps
|
| 337 |
+
return cs.chain_schema(chain_schema_steps)
|
| 338 |
+
|
| 339 |
+
return schema
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
def collect_known_metadata(annotations: Iterable[Any]) -> tuple[dict[str, Any], list[Any]]:
|
| 343 |
+
"""Split `annotations` into known metadata and unknown annotations.
|
| 344 |
+
|
| 345 |
+
Args:
|
| 346 |
+
annotations: An iterable of annotations.
|
| 347 |
+
|
| 348 |
+
Returns:
|
| 349 |
+
A tuple contains a dict of known metadata and a list of unknown annotations.
|
| 350 |
+
|
| 351 |
+
Example:
|
| 352 |
+
```python
|
| 353 |
+
from annotated_types import Gt, Len
|
| 354 |
+
|
| 355 |
+
from pydantic._internal._known_annotated_metadata import collect_known_metadata
|
| 356 |
+
|
| 357 |
+
print(collect_known_metadata([Gt(1), Len(42), ...]))
|
| 358 |
+
#> ({'gt': 1, 'min_length': 42}, [Ellipsis])
|
| 359 |
+
```
|
| 360 |
+
"""
|
| 361 |
+
annotations = expand_grouped_metadata(annotations)
|
| 362 |
+
|
| 363 |
+
res: dict[str, Any] = {}
|
| 364 |
+
remaining: list[Any] = []
|
| 365 |
+
|
| 366 |
+
for annotation in annotations:
|
| 367 |
+
# isinstance(annotation, PydanticMetadata) also covers ._fields:_PydanticGeneralMetadata
|
| 368 |
+
if isinstance(annotation, PydanticMetadata):
|
| 369 |
+
res.update(annotation.__dict__)
|
| 370 |
+
# we don't use dataclasses.asdict because that recursively calls asdict on the field values
|
| 371 |
+
elif (annotation_type := type(annotation)) in (at_to_constraint_map := _get_at_to_constraint_map()):
|
| 372 |
+
constraint = at_to_constraint_map[annotation_type]
|
| 373 |
+
res[constraint] = getattr(annotation, constraint)
|
| 374 |
+
elif isinstance(annotation, type) and issubclass(annotation, PydanticMetadata):
|
| 375 |
+
# also support PydanticMetadata classes being used without initialisation,
|
| 376 |
+
# e.g. `Annotated[int, Strict]` as well as `Annotated[int, Strict()]`
|
| 377 |
+
res.update({k: v for k, v in vars(annotation).items() if not k.startswith('_')})
|
| 378 |
+
else:
|
| 379 |
+
remaining.append(annotation)
|
| 380 |
+
# Nones can sneak in but pydantic-core will reject them
|
| 381 |
+
# it'd be nice to clean things up so we don't put in None (we probably don't _need_ to, it was just easier)
|
| 382 |
+
# but this is simple enough to kick that can down the road
|
| 383 |
+
res = {k: v for k, v in res.items() if v is not None}
|
| 384 |
+
return res, remaining
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
def check_metadata(metadata: dict[str, Any], allowed: Iterable[str], source_type: Any) -> None:
|
| 388 |
+
"""A small utility function to validate that the given metadata can be applied to the target.
|
| 389 |
+
More than saving lines of code, this gives us a consistent error message for all of our internal implementations.
|
| 390 |
+
|
| 391 |
+
Args:
|
| 392 |
+
metadata: A dict of metadata.
|
| 393 |
+
allowed: An iterable of allowed metadata.
|
| 394 |
+
source_type: The source type.
|
| 395 |
+
|
| 396 |
+
Raises:
|
| 397 |
+
TypeError: If there is metadatas that can't be applied on source type.
|
| 398 |
+
"""
|
| 399 |
+
unknown = metadata.keys() - set(allowed)
|
| 400 |
+
if unknown:
|
| 401 |
+
raise TypeError(
|
| 402 |
+
f'The following constraints cannot be applied to {source_type!r}: {", ".join([f"{k!r}" for k in unknown])}'
|
| 403 |
+
)
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_mock_val_ser.py
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from collections.abc import Iterator, Mapping
|
| 4 |
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, TypeVar, Union
|
| 5 |
+
|
| 6 |
+
from pydantic_core import CoreSchema, SchemaSerializer, SchemaValidator
|
| 7 |
+
|
| 8 |
+
from ..errors import PydanticErrorCodes, PydanticUserError
|
| 9 |
+
from ..plugin._schema_validator import PluggableSchemaValidator
|
| 10 |
+
|
| 11 |
+
if TYPE_CHECKING:
|
| 12 |
+
from ..dataclasses import PydanticDataclass
|
| 13 |
+
from ..main import BaseModel
|
| 14 |
+
from ..type_adapter import TypeAdapter
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
ValSer = TypeVar('ValSer', bound=Union[SchemaValidator, PluggableSchemaValidator, SchemaSerializer])
|
| 18 |
+
T = TypeVar('T')
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class MockCoreSchema(Mapping[str, Any]):
|
| 22 |
+
"""Mocker for `pydantic_core.CoreSchema` which optionally attempts to
|
| 23 |
+
rebuild the thing it's mocking when one of its methods is accessed and raises an error if that fails.
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
__slots__ = '_error_message', '_code', '_attempt_rebuild', '_built_memo'
|
| 27 |
+
|
| 28 |
+
def __init__(
|
| 29 |
+
self,
|
| 30 |
+
error_message: str,
|
| 31 |
+
*,
|
| 32 |
+
code: PydanticErrorCodes,
|
| 33 |
+
attempt_rebuild: Callable[[], CoreSchema | None] | None = None,
|
| 34 |
+
) -> None:
|
| 35 |
+
self._error_message = error_message
|
| 36 |
+
self._code: PydanticErrorCodes = code
|
| 37 |
+
self._attempt_rebuild = attempt_rebuild
|
| 38 |
+
self._built_memo: CoreSchema | None = None
|
| 39 |
+
|
| 40 |
+
def __getitem__(self, key: str) -> Any:
|
| 41 |
+
return self._get_built().__getitem__(key)
|
| 42 |
+
|
| 43 |
+
def __len__(self) -> int:
|
| 44 |
+
return self._get_built().__len__()
|
| 45 |
+
|
| 46 |
+
def __iter__(self) -> Iterator[str]:
|
| 47 |
+
return self._get_built().__iter__()
|
| 48 |
+
|
| 49 |
+
def _get_built(self) -> CoreSchema:
|
| 50 |
+
if self._built_memo is not None:
|
| 51 |
+
return self._built_memo
|
| 52 |
+
|
| 53 |
+
if self._attempt_rebuild:
|
| 54 |
+
schema = self._attempt_rebuild()
|
| 55 |
+
if schema is not None:
|
| 56 |
+
self._built_memo = schema
|
| 57 |
+
return schema
|
| 58 |
+
raise PydanticUserError(self._error_message, code=self._code)
|
| 59 |
+
|
| 60 |
+
def rebuild(self) -> CoreSchema | None:
|
| 61 |
+
self._built_memo = None
|
| 62 |
+
if self._attempt_rebuild:
|
| 63 |
+
schema = self._attempt_rebuild()
|
| 64 |
+
if schema is not None:
|
| 65 |
+
return schema
|
| 66 |
+
else:
|
| 67 |
+
raise PydanticUserError(self._error_message, code=self._code)
|
| 68 |
+
return None
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class MockValSer(Generic[ValSer]):
|
| 72 |
+
"""Mocker for `pydantic_core.SchemaValidator` or `pydantic_core.SchemaSerializer` which optionally attempts to
|
| 73 |
+
rebuild the thing it's mocking when one of its methods is accessed and raises an error if that fails.
|
| 74 |
+
"""
|
| 75 |
+
|
| 76 |
+
__slots__ = '_error_message', '_code', '_val_or_ser', '_attempt_rebuild'
|
| 77 |
+
|
| 78 |
+
def __init__(
|
| 79 |
+
self,
|
| 80 |
+
error_message: str,
|
| 81 |
+
*,
|
| 82 |
+
code: PydanticErrorCodes,
|
| 83 |
+
val_or_ser: Literal['validator', 'serializer'],
|
| 84 |
+
attempt_rebuild: Callable[[], ValSer | None] | None = None,
|
| 85 |
+
) -> None:
|
| 86 |
+
self._error_message = error_message
|
| 87 |
+
self._val_or_ser = SchemaValidator if val_or_ser == 'validator' else SchemaSerializer
|
| 88 |
+
self._code: PydanticErrorCodes = code
|
| 89 |
+
self._attempt_rebuild = attempt_rebuild
|
| 90 |
+
|
| 91 |
+
def __getattr__(self, item: str) -> None:
|
| 92 |
+
__tracebackhide__ = True
|
| 93 |
+
if self._attempt_rebuild:
|
| 94 |
+
val_ser = self._attempt_rebuild()
|
| 95 |
+
if val_ser is not None:
|
| 96 |
+
return getattr(val_ser, item)
|
| 97 |
+
|
| 98 |
+
# raise an AttributeError if `item` doesn't exist
|
| 99 |
+
getattr(self._val_or_ser, item)
|
| 100 |
+
raise PydanticUserError(self._error_message, code=self._code)
|
| 101 |
+
|
| 102 |
+
def rebuild(self) -> ValSer | None:
|
| 103 |
+
if self._attempt_rebuild:
|
| 104 |
+
val_ser = self._attempt_rebuild()
|
| 105 |
+
if val_ser is not None:
|
| 106 |
+
return val_ser
|
| 107 |
+
else:
|
| 108 |
+
raise PydanticUserError(self._error_message, code=self._code)
|
| 109 |
+
return None
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def set_type_adapter_mocks(adapter: TypeAdapter) -> None:
|
| 113 |
+
"""Set `core_schema`, `validator` and `serializer` to mock core types on a type adapter instance.
|
| 114 |
+
|
| 115 |
+
Args:
|
| 116 |
+
adapter: The type adapter instance to set the mocks on
|
| 117 |
+
"""
|
| 118 |
+
type_repr = str(adapter._type)
|
| 119 |
+
undefined_type_error_message = (
|
| 120 |
+
f'`TypeAdapter[{type_repr}]` is not fully defined; you should define `{type_repr}` and all referenced types,'
|
| 121 |
+
f' then call `.rebuild()` on the instance.'
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
def attempt_rebuild_fn(attr_fn: Callable[[TypeAdapter], T]) -> Callable[[], T | None]:
|
| 125 |
+
def handler() -> T | None:
|
| 126 |
+
if adapter.rebuild(raise_errors=False, _parent_namespace_depth=5) is not False:
|
| 127 |
+
return attr_fn(adapter)
|
| 128 |
+
return None
|
| 129 |
+
|
| 130 |
+
return handler
|
| 131 |
+
|
| 132 |
+
adapter.core_schema = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue]
|
| 133 |
+
undefined_type_error_message,
|
| 134 |
+
code='class-not-fully-defined',
|
| 135 |
+
attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.core_schema),
|
| 136 |
+
)
|
| 137 |
+
adapter.validator = MockValSer( # pyright: ignore[reportAttributeAccessIssue]
|
| 138 |
+
undefined_type_error_message,
|
| 139 |
+
code='class-not-fully-defined',
|
| 140 |
+
val_or_ser='validator',
|
| 141 |
+
attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.validator),
|
| 142 |
+
)
|
| 143 |
+
adapter.serializer = MockValSer( # pyright: ignore[reportAttributeAccessIssue]
|
| 144 |
+
undefined_type_error_message,
|
| 145 |
+
code='class-not-fully-defined',
|
| 146 |
+
val_or_ser='serializer',
|
| 147 |
+
attempt_rebuild=attempt_rebuild_fn(lambda ta: ta.serializer),
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def set_model_mocks(cls: type[BaseModel], undefined_name: str = 'all referenced types') -> None:
|
| 152 |
+
"""Set `__pydantic_core_schema__`, `__pydantic_validator__` and `__pydantic_serializer__` to mock core types on a model.
|
| 153 |
+
|
| 154 |
+
Args:
|
| 155 |
+
cls: The model class to set the mocks on
|
| 156 |
+
undefined_name: Name of the undefined thing, used in error messages
|
| 157 |
+
"""
|
| 158 |
+
undefined_type_error_message = (
|
| 159 |
+
f'`{cls.__name__}` is not fully defined; you should define {undefined_name},'
|
| 160 |
+
f' then call `{cls.__name__}.model_rebuild()`.'
|
| 161 |
+
)
|
| 162 |
+
|
| 163 |
+
def attempt_rebuild_fn(attr_fn: Callable[[type[BaseModel]], T]) -> Callable[[], T | None]:
|
| 164 |
+
def handler() -> T | None:
|
| 165 |
+
if cls.model_rebuild(raise_errors=False, _parent_namespace_depth=5) is not False:
|
| 166 |
+
return attr_fn(cls)
|
| 167 |
+
return None
|
| 168 |
+
|
| 169 |
+
return handler
|
| 170 |
+
|
| 171 |
+
cls.__pydantic_core_schema__ = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue]
|
| 172 |
+
undefined_type_error_message,
|
| 173 |
+
code='class-not-fully-defined',
|
| 174 |
+
attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_core_schema__),
|
| 175 |
+
)
|
| 176 |
+
cls.__pydantic_validator__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue]
|
| 177 |
+
undefined_type_error_message,
|
| 178 |
+
code='class-not-fully-defined',
|
| 179 |
+
val_or_ser='validator',
|
| 180 |
+
attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_validator__),
|
| 181 |
+
)
|
| 182 |
+
cls.__pydantic_serializer__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue]
|
| 183 |
+
undefined_type_error_message,
|
| 184 |
+
code='class-not-fully-defined',
|
| 185 |
+
val_or_ser='serializer',
|
| 186 |
+
attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_serializer__),
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def set_dataclass_mocks(cls: type[PydanticDataclass], undefined_name: str = 'all referenced types') -> None:
|
| 191 |
+
"""Set `__pydantic_validator__` and `__pydantic_serializer__` to `MockValSer`s on a dataclass.
|
| 192 |
+
|
| 193 |
+
Args:
|
| 194 |
+
cls: The model class to set the mocks on
|
| 195 |
+
undefined_name: Name of the undefined thing, used in error messages
|
| 196 |
+
"""
|
| 197 |
+
from ..dataclasses import rebuild_dataclass
|
| 198 |
+
|
| 199 |
+
undefined_type_error_message = (
|
| 200 |
+
f'`{cls.__name__}` is not fully defined; you should define {undefined_name},'
|
| 201 |
+
f' then call `pydantic.dataclasses.rebuild_dataclass({cls.__name__})`.'
|
| 202 |
+
)
|
| 203 |
+
|
| 204 |
+
def attempt_rebuild_fn(attr_fn: Callable[[type[PydanticDataclass]], T]) -> Callable[[], T | None]:
|
| 205 |
+
def handler() -> T | None:
|
| 206 |
+
if rebuild_dataclass(cls, raise_errors=False, _parent_namespace_depth=5) is not False:
|
| 207 |
+
return attr_fn(cls)
|
| 208 |
+
return None
|
| 209 |
+
|
| 210 |
+
return handler
|
| 211 |
+
|
| 212 |
+
cls.__pydantic_core_schema__ = MockCoreSchema( # pyright: ignore[reportAttributeAccessIssue]
|
| 213 |
+
undefined_type_error_message,
|
| 214 |
+
code='class-not-fully-defined',
|
| 215 |
+
attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_core_schema__),
|
| 216 |
+
)
|
| 217 |
+
cls.__pydantic_validator__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue]
|
| 218 |
+
undefined_type_error_message,
|
| 219 |
+
code='class-not-fully-defined',
|
| 220 |
+
val_or_ser='validator',
|
| 221 |
+
attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_validator__),
|
| 222 |
+
)
|
| 223 |
+
cls.__pydantic_serializer__ = MockValSer( # pyright: ignore[reportAttributeAccessIssue]
|
| 224 |
+
undefined_type_error_message,
|
| 225 |
+
code='class-not-fully-defined',
|
| 226 |
+
val_or_ser='serializer',
|
| 227 |
+
attempt_rebuild=attempt_rebuild_fn(lambda c: c.__pydantic_serializer__),
|
| 228 |
+
)
|
personalPromptBoost/.venv/lib/python3.13/site-packages/pydantic/_internal/_model_construction.py
ADDED
|
@@ -0,0 +1,868 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Private logic for creating models."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations as _annotations
|
| 4 |
+
|
| 5 |
+
import operator
|
| 6 |
+
import sys
|
| 7 |
+
import typing
|
| 8 |
+
import warnings
|
| 9 |
+
import weakref
|
| 10 |
+
from abc import ABCMeta
|
| 11 |
+
from functools import cache, partial, wraps
|
| 12 |
+
from types import FunctionType
|
| 13 |
+
from typing import TYPE_CHECKING, Any, Callable, Generic, Literal, NoReturn, TypeVar, cast
|
| 14 |
+
|
| 15 |
+
from pydantic_core import PydanticUndefined, SchemaSerializer
|
| 16 |
+
from typing_extensions import TypeAliasType, dataclass_transform, deprecated, get_args, get_origin
|
| 17 |
+
from typing_inspection import typing_objects
|
| 18 |
+
|
| 19 |
+
from ..errors import PydanticUndefinedAnnotation, PydanticUserError
|
| 20 |
+
from ..plugin._schema_validator import create_schema_validator
|
| 21 |
+
from ..warnings import GenericBeforeBaseModelWarning, PydanticDeprecatedSince20
|
| 22 |
+
from ._config import ConfigWrapper
|
| 23 |
+
from ._decorators import DecoratorInfos, PydanticDescriptorProxy, get_attribute_from_bases, unwrap_wrapped_function
|
| 24 |
+
from ._fields import collect_model_fields, is_valid_field_name, is_valid_privateattr_name, rebuild_model_fields
|
| 25 |
+
from ._generate_schema import GenerateSchema, InvalidSchemaError
|
| 26 |
+
from ._generics import PydanticGenericMetadata, get_model_typevars_map
|
| 27 |
+
from ._import_utils import import_cached_base_model, import_cached_field_info
|
| 28 |
+
from ._mock_val_ser import set_model_mocks
|
| 29 |
+
from ._namespace_utils import NsResolver
|
| 30 |
+
from ._signature import generate_pydantic_signature
|
| 31 |
+
from ._typing_extra import (
|
| 32 |
+
_make_forward_ref,
|
| 33 |
+
eval_type_backport,
|
| 34 |
+
is_classvar_annotation,
|
| 35 |
+
parent_frame_namespace,
|
| 36 |
+
)
|
| 37 |
+
from ._utils import LazyClassAttribute, SafeGetItemProxy
|
| 38 |
+
|
| 39 |
+
if TYPE_CHECKING:
|
| 40 |
+
from ..fields import Field as PydanticModelField
|
| 41 |
+
from ..fields import FieldInfo, ModelPrivateAttr
|
| 42 |
+
from ..fields import PrivateAttr as PydanticModelPrivateAttr
|
| 43 |
+
from ..main import BaseModel
|
| 44 |
+
from ._fields import PydanticExtraInfo
|
| 45 |
+
else:
|
| 46 |
+
PydanticModelField = object()
|
| 47 |
+
PydanticModelPrivateAttr = object()
|
| 48 |
+
|
| 49 |
+
object_setattr = object.__setattr__
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class _ModelNamespaceDict(dict):
|
| 53 |
+
"""A dictionary subclass that intercepts attribute setting on model classes and
|
| 54 |
+
warns about overriding of decorators.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
def __setitem__(self, k: str, v: object) -> None:
|
| 58 |
+
existing: Any = self.get(k, None)
|
| 59 |
+
if existing and v is not existing and isinstance(existing, PydanticDescriptorProxy):
|
| 60 |
+
warnings.warn(
|
| 61 |
+
f'`{k}` overrides an existing Pydantic `{existing.decorator_info.decorator_repr}` decorator',
|
| 62 |
+
stacklevel=2,
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
return super().__setitem__(k, v)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def NoInitField(
|
| 69 |
+
*,
|
| 70 |
+
init: Literal[False] = False,
|
| 71 |
+
) -> Any:
|
| 72 |
+
"""Only for typing purposes. Used as default value of `__pydantic_fields_set__`,
|
| 73 |
+
`__pydantic_extra__`, `__pydantic_private__`, so they could be ignored when
|
| 74 |
+
synthesizing the `__init__` signature.
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
# For ModelMetaclass.register():
|
| 79 |
+
_T = TypeVar('_T')
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@dataclass_transform(kw_only_default=True, field_specifiers=(PydanticModelField, PydanticModelPrivateAttr, NoInitField))
|
| 83 |
+
class ModelMetaclass(ABCMeta):
|
| 84 |
+
def __new__(
|
| 85 |
+
mcs,
|
| 86 |
+
cls_name: str,
|
| 87 |
+
bases: tuple[type[Any], ...],
|
| 88 |
+
namespace: dict[str, Any],
|
| 89 |
+
__pydantic_generic_metadata__: PydanticGenericMetadata | None = None,
|
| 90 |
+
__pydantic_reset_parent_namespace__: bool = True,
|
| 91 |
+
_create_model_module: str | None = None,
|
| 92 |
+
**kwargs: Any,
|
| 93 |
+
) -> type:
|
| 94 |
+
"""Metaclass for creating Pydantic models.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
cls_name: The name of the class to be created.
|
| 98 |
+
bases: The base classes of the class to be created.
|
| 99 |
+
namespace: The attribute dictionary of the class to be created.
|
| 100 |
+
__pydantic_generic_metadata__: Metadata for generic models.
|
| 101 |
+
__pydantic_reset_parent_namespace__: Reset parent namespace.
|
| 102 |
+
_create_model_module: The module of the class to be created, if created by `create_model`.
|
| 103 |
+
**kwargs: Catch-all for any other keyword arguments.
|
| 104 |
+
|
| 105 |
+
Returns:
|
| 106 |
+
The new class created by the metaclass.
|
| 107 |
+
"""
|
| 108 |
+
# Note `ModelMetaclass` refers to `BaseModel`, but is also used to *create* `BaseModel`, so we rely on the fact
|
| 109 |
+
# that `BaseModel` itself won't have any bases, but any subclass of it will, to determine whether the `__new__`
|
| 110 |
+
# call we're in the middle of is for the `BaseModel` class.
|
| 111 |
+
if bases:
|
| 112 |
+
raw_annotations: dict[str, Any]
|
| 113 |
+
if sys.version_info >= (3, 14):
|
| 114 |
+
if (
|
| 115 |
+
'__annotations__' in namespace
|
| 116 |
+
): # `from __future__ import annotations` was used in the model's module
|
| 117 |
+
raw_annotations = namespace['__annotations__']
|
| 118 |
+
else:
|
| 119 |
+
# See https://docs.python.org/3.14/library/annotationlib.html#using-annotations-in-a-metaclass:
|
| 120 |
+
from annotationlib import Format, call_annotate_function, get_annotate_from_class_namespace
|
| 121 |
+
|
| 122 |
+
if annotate := get_annotate_from_class_namespace(namespace):
|
| 123 |
+
raw_annotations = call_annotate_function(annotate, format=Format.FORWARDREF)
|
| 124 |
+
else:
|
| 125 |
+
raw_annotations = {}
|
| 126 |
+
else:
|
| 127 |
+
raw_annotations = namespace.get('__annotations__', {})
|
| 128 |
+
|
| 129 |
+
base_field_names, class_vars, base_private_attributes = mcs._collect_bases_data(bases)
|
| 130 |
+
|
| 131 |
+
config_wrapper = ConfigWrapper.for_model(bases, namespace, raw_annotations, kwargs)
|
| 132 |
+
namespace['model_config'] = config_wrapper.config_dict
|
| 133 |
+
private_attributes = inspect_namespace(
|
| 134 |
+
namespace, raw_annotations, config_wrapper.ignored_types, class_vars, base_field_names
|
| 135 |
+
)
|
| 136 |
+
if private_attributes or base_private_attributes:
|
| 137 |
+
original_model_post_init = get_model_post_init(namespace, bases)
|
| 138 |
+
if original_model_post_init is not None:
|
| 139 |
+
# if there are private attributes and a model_post_init function, we handle both
|
| 140 |
+
|
| 141 |
+
@wraps(original_model_post_init)
|
| 142 |
+
def wrapped_model_post_init(self: BaseModel, context: Any, /) -> None:
|
| 143 |
+
"""We need to both initialize private attributes and call the user-defined model_post_init
|
| 144 |
+
method.
|
| 145 |
+
"""
|
| 146 |
+
init_private_attributes(self, context)
|
| 147 |
+
original_model_post_init(self, context)
|
| 148 |
+
|
| 149 |
+
namespace['model_post_init'] = wrapped_model_post_init
|
| 150 |
+
else:
|
| 151 |
+
namespace['model_post_init'] = init_private_attributes
|
| 152 |
+
|
| 153 |
+
namespace['__class_vars__'] = class_vars
|
| 154 |
+
namespace['__private_attributes__'] = {**base_private_attributes, **private_attributes}
|
| 155 |
+
|
| 156 |
+
cls = cast('type[BaseModel]', super().__new__(mcs, cls_name, bases, namespace, **kwargs))
|
| 157 |
+
BaseModel_ = import_cached_base_model()
|
| 158 |
+
|
| 159 |
+
mro = cls.__mro__
|
| 160 |
+
if Generic in mro and mro.index(Generic) < mro.index(BaseModel_):
|
| 161 |
+
warnings.warn(
|
| 162 |
+
GenericBeforeBaseModelWarning(
|
| 163 |
+
'Classes should inherit from `BaseModel` before generic classes (e.g. `typing.Generic[T]`) '
|
| 164 |
+
'for pydantic generics to work properly.'
|
| 165 |
+
),
|
| 166 |
+
stacklevel=2,
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
cls.__pydantic_custom_init__ = not getattr(cls.__init__, '__pydantic_base_init__', False)
|
| 170 |
+
cls.__pydantic_post_init__ = (
|
| 171 |
+
None if cls.model_post_init is BaseModel_.model_post_init else 'model_post_init'
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
cls.__pydantic_setattr_handlers__ = {}
|
| 175 |
+
|
| 176 |
+
cls.__pydantic_decorators__ = DecoratorInfos.build(cls, replace_wrapped_methods=True)
|
| 177 |
+
cls.__pydantic_decorators__.update_from_config(config_wrapper)
|
| 178 |
+
|
| 179 |
+
# Use the getattr below to grab the __parameters__ from the `typing.Generic` parent class
|
| 180 |
+
if __pydantic_generic_metadata__:
|
| 181 |
+
cls.__pydantic_generic_metadata__ = __pydantic_generic_metadata__
|
| 182 |
+
else:
|
| 183 |
+
parent_parameters = getattr(cls, '__pydantic_generic_metadata__', {}).get('parameters', ())
|
| 184 |
+
parameters = getattr(cls, '__parameters__', None) or parent_parameters
|
| 185 |
+
if parameters and parent_parameters and not all(x in parameters for x in parent_parameters):
|
| 186 |
+
from ..root_model import RootModelRootType
|
| 187 |
+
|
| 188 |
+
missing_parameters = tuple(x for x in parameters if x not in parent_parameters)
|
| 189 |
+
if RootModelRootType in parent_parameters and RootModelRootType not in parameters:
|
| 190 |
+
# This is a special case where the user has subclassed RootModel, but has not parameterized
|
| 191 |
+
# RootModel with the generic type identifiers being used. Ex:
|
| 192 |
+
# class MyModel(RootModel, Generic[T]):
|
| 193 |
+
# root: T
|
| 194 |
+
# Should instead just be:
|
| 195 |
+
# class MyModel(RootModel[T]):
|
| 196 |
+
# root: T
|
| 197 |
+
parameters_str = ', '.join([x.__name__ for x in missing_parameters])
|
| 198 |
+
error_message = (
|
| 199 |
+
f'{cls.__name__} is a subclass of `RootModel`, but does not include the generic type identifier(s) '
|
| 200 |
+
f'{parameters_str} in its parameters. '
|
| 201 |
+
f'You should parametrize RootModel directly, e.g., `class {cls.__name__}(RootModel[{parameters_str}]): ...`.'
|
| 202 |
+
)
|
| 203 |
+
else:
|
| 204 |
+
combined_parameters = parent_parameters + missing_parameters
|
| 205 |
+
parameters_str = ', '.join([str(x) for x in combined_parameters])
|
| 206 |
+
generic_type_label = f'typing.Generic[{parameters_str}]'
|
| 207 |
+
error_message = (
|
| 208 |
+
f'All parameters must be present on typing.Generic;'
|
| 209 |
+
f' you should inherit from {generic_type_label}.'
|
| 210 |
+
)
|
| 211 |
+
if Generic not in bases: # pragma: no cover
|
| 212 |
+
# We raise an error here not because it is desirable, but because some cases are mishandled.
|
| 213 |
+
# It would be nice to remove this error and still have things behave as expected, it's just
|
| 214 |
+
# challenging because we are using a custom `__class_getitem__` to parametrize generic models,
|
| 215 |
+
# and not returning a typing._GenericAlias from it.
|
| 216 |
+
bases_str = ', '.join([x.__name__ for x in bases] + [generic_type_label])
|
| 217 |
+
error_message += (
|
| 218 |
+
f' Note: `typing.Generic` must go last: `class {cls.__name__}({bases_str}): ...`)'
|
| 219 |
+
)
|
| 220 |
+
raise TypeError(error_message)
|
| 221 |
+
|
| 222 |
+
cls.__pydantic_generic_metadata__ = {
|
| 223 |
+
'origin': None,
|
| 224 |
+
'args': (),
|
| 225 |
+
'parameters': parameters,
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
cls.__pydantic_complete__ = False # Ensure this specific class gets completed
|
| 229 |
+
|
| 230 |
+
# preserve `__set_name__` protocol defined in https://peps.python.org/pep-0487
|
| 231 |
+
# for attributes not in `new_namespace` (e.g. private attributes)
|
| 232 |
+
for name, obj in private_attributes.items():
|
| 233 |
+
obj.__set_name__(cls, name)
|
| 234 |
+
|
| 235 |
+
if __pydantic_reset_parent_namespace__:
|
| 236 |
+
cls.__pydantic_parent_namespace__ = build_lenient_weakvaluedict(parent_frame_namespace())
|
| 237 |
+
parent_namespace: dict[str, Any] | None = getattr(cls, '__pydantic_parent_namespace__', None)
|
| 238 |
+
if isinstance(parent_namespace, dict):
|
| 239 |
+
parent_namespace = unpack_lenient_weakvaluedict(parent_namespace)
|
| 240 |
+
|
| 241 |
+
ns_resolver = NsResolver(parent_namespace=parent_namespace)
|
| 242 |
+
|
| 243 |
+
set_model_fields(cls, config_wrapper=config_wrapper, ns_resolver=ns_resolver)
|
| 244 |
+
|
| 245 |
+
# This is also set in `complete_model_class()`, after schema gen because they are recreated.
|
| 246 |
+
# We set them here as well for backwards compatibility:
|
| 247 |
+
cls.__pydantic_computed_fields__ = {
|
| 248 |
+
k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items()
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
if config_wrapper.defer_build:
|
| 252 |
+
set_model_mocks(cls)
|
| 253 |
+
else:
|
| 254 |
+
# Any operation that requires accessing the field infos instances should be put inside
|
| 255 |
+
# `complete_model_class()`:
|
| 256 |
+
complete_model_class(
|
| 257 |
+
cls,
|
| 258 |
+
config_wrapper,
|
| 259 |
+
ns_resolver,
|
| 260 |
+
raise_errors=False,
|
| 261 |
+
create_model_module=_create_model_module,
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
if config_wrapper.frozen and '__hash__' not in namespace:
|
| 265 |
+
set_default_hash_func(cls, bases)
|
| 266 |
+
|
| 267 |
+
# using super(cls, cls) on the next line ensures we only call the parent class's __pydantic_init_subclass__
|
| 268 |
+
# I believe the `type: ignore` is only necessary because mypy doesn't realize that this code branch is
|
| 269 |
+
# only hit for _proper_ subclasses of BaseModel
|
| 270 |
+
super(cls, cls).__pydantic_init_subclass__(**kwargs) # type: ignore[misc]
|
| 271 |
+
return cls
|
| 272 |
+
else:
|
| 273 |
+
# These are instance variables, but have been assigned to `NoInitField` to trick the type checker.
|
| 274 |
+
for instance_slot in '__pydantic_fields_set__', '__pydantic_extra__', '__pydantic_private__':
|
| 275 |
+
namespace.pop(
|
| 276 |
+
instance_slot,
|
| 277 |
+
None, # In case the metaclass is used with a class other than `BaseModel`.
|
| 278 |
+
)
|
| 279 |
+
namespace.get('__annotations__', {}).clear()
|
| 280 |
+
return super().__new__(mcs, cls_name, bases, namespace, **kwargs)
|
| 281 |
+
|
| 282 |
+
if not TYPE_CHECKING: # pragma: no branch
|
| 283 |
+
# We put `__getattr__` in a non-TYPE_CHECKING block because otherwise, mypy allows arbitrary attribute access
|
| 284 |
+
|
| 285 |
+
def __getattr__(self, item: str) -> Any:
|
| 286 |
+
"""This is necessary to keep attribute access working for class attribute access."""
|
| 287 |
+
private_attributes = self.__dict__.get('__private_attributes__')
|
| 288 |
+
if private_attributes and item in private_attributes:
|
| 289 |
+
return private_attributes[item]
|
| 290 |
+
raise AttributeError(item)
|
| 291 |
+
|
| 292 |
+
@classmethod
|
| 293 |
+
def __prepare__(cls, *args: Any, **kwargs: Any) -> dict[str, object]:
|
| 294 |
+
return _ModelNamespaceDict()
|
| 295 |
+
|
| 296 |
+
# Due to performance and memory issues, in the ABCMeta.__subclasscheck__ implementation, we don't support
|
| 297 |
+
# registered virtual subclasses. See https://github.com/python/cpython/issues/92810#issuecomment-2762454345.
|
| 298 |
+
# This may change once CPython is fixed (possibly in 3.15), in which case we should conditionally
|
| 299 |
+
# define `register()`.
|
| 300 |
+
def register(self, subclass: type[_T]) -> type[_T]:
|
| 301 |
+
warnings.warn(
|
| 302 |
+
f"For performance reasons, virtual subclasses registered using '{self.__qualname__}.register()' "
|
| 303 |
+
"are not supported in 'isinstance()' and 'issubclass()' checks.",
|
| 304 |
+
stacklevel=2,
|
| 305 |
+
)
|
| 306 |
+
return super().register(subclass)
|
| 307 |
+
|
| 308 |
+
__instancecheck__ = type.__instancecheck__ # pyright: ignore[reportAssignmentType]
|
| 309 |
+
__subclasscheck__ = type.__subclasscheck__ # pyright: ignore[reportAssignmentType]
|
| 310 |
+
|
| 311 |
+
@staticmethod
|
| 312 |
+
def _collect_bases_data(bases: tuple[type[Any], ...]) -> tuple[set[str], set[str], dict[str, ModelPrivateAttr]]:
|
| 313 |
+
BaseModel = import_cached_base_model()
|
| 314 |
+
|
| 315 |
+
field_names: set[str] = set()
|
| 316 |
+
class_vars: set[str] = set()
|
| 317 |
+
private_attributes: dict[str, ModelPrivateAttr] = {}
|
| 318 |
+
for base in bases:
|
| 319 |
+
if issubclass(base, BaseModel) and base is not BaseModel:
|
| 320 |
+
# model_fields might not be defined yet in the case of generics, so we use getattr here:
|
| 321 |
+
field_names.update(getattr(base, '__pydantic_fields__', {}).keys())
|
| 322 |
+
class_vars.update(base.__class_vars__)
|
| 323 |
+
private_attributes.update(base.__private_attributes__)
|
| 324 |
+
return field_names, class_vars, private_attributes
|
| 325 |
+
|
| 326 |
+
@property
|
| 327 |
+
@deprecated(
|
| 328 |
+
'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.', category=None
|
| 329 |
+
)
|
| 330 |
+
def __fields__(self) -> dict[str, FieldInfo]:
|
| 331 |
+
warnings.warn(
|
| 332 |
+
'The `__fields__` attribute is deprecated, use the `model_fields` class property instead.',
|
| 333 |
+
PydanticDeprecatedSince20,
|
| 334 |
+
stacklevel=2,
|
| 335 |
+
)
|
| 336 |
+
return getattr(self, '__pydantic_fields__', {})
|
| 337 |
+
|
| 338 |
+
@property
|
| 339 |
+
def __pydantic_fields_complete__(self) -> bool:
|
| 340 |
+
"""Whether the fields were successfully collected (i.e. type hints were successfully resolved).
|
| 341 |
+
|
| 342 |
+
This is a private attribute, not meant to be used outside Pydantic.
|
| 343 |
+
"""
|
| 344 |
+
if '__pydantic_fields__' not in self.__dict__:
|
| 345 |
+
return False
|
| 346 |
+
|
| 347 |
+
field_infos = cast('dict[str, FieldInfo]', self.__pydantic_fields__) # pyright: ignore[reportAttributeAccessIssue]
|
| 348 |
+
|
| 349 |
+
pydantic_extra_info = cast('PydanticExtraInfo | None', self.__pydantic_extra_info__) # pyright: ignore[reportAttributeAccessIssue]
|
| 350 |
+
if pydantic_extra_info is not None:
|
| 351 |
+
extra_complete = pydantic_extra_info.complete
|
| 352 |
+
else:
|
| 353 |
+
extra_complete = True
|
| 354 |
+
|
| 355 |
+
return all(field_info._complete for field_info in field_infos.values()) and extra_complete
|
| 356 |
+
|
| 357 |
+
def __dir__(self) -> list[str]:
|
| 358 |
+
attributes = list(super().__dir__())
|
| 359 |
+
if '__fields__' in attributes:
|
| 360 |
+
attributes.remove('__fields__')
|
| 361 |
+
return attributes
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def init_private_attributes(self: BaseModel, context: Any, /) -> None:
|
| 365 |
+
"""This function is meant to behave like a BaseModel method to initialize private attributes.
|
| 366 |
+
|
| 367 |
+
It takes context as an argument since that's what pydantic-core passes when calling it.
|
| 368 |
+
|
| 369 |
+
Args:
|
| 370 |
+
self: The BaseModel instance.
|
| 371 |
+
context: The context.
|
| 372 |
+
"""
|
| 373 |
+
if getattr(self, '__pydantic_private__', None) is None:
|
| 374 |
+
pydantic_private = {}
|
| 375 |
+
for name, private_attr in self.__private_attributes__.items():
|
| 376 |
+
# Avoid needlessly creating a new dict for the validated data:
|
| 377 |
+
if private_attr.default_factory_takes_validated_data:
|
| 378 |
+
default = private_attr.get_default(
|
| 379 |
+
call_default_factory=True, validated_data={**self.__dict__, **pydantic_private}
|
| 380 |
+
)
|
| 381 |
+
else:
|
| 382 |
+
default = private_attr.get_default(call_default_factory=True)
|
| 383 |
+
if default is not PydanticUndefined:
|
| 384 |
+
pydantic_private[name] = default
|
| 385 |
+
object_setattr(self, '__pydantic_private__', pydantic_private)
|
| 386 |
+
|
| 387 |
+
|
| 388 |
+
def get_model_post_init(namespace: dict[str, Any], bases: tuple[type[Any], ...]) -> Callable[..., Any] | None:
|
| 389 |
+
"""Get the `model_post_init` method from the namespace or the class bases, or `None` if not defined."""
|
| 390 |
+
if 'model_post_init' in namespace:
|
| 391 |
+
return namespace['model_post_init']
|
| 392 |
+
|
| 393 |
+
BaseModel = import_cached_base_model()
|
| 394 |
+
|
| 395 |
+
model_post_init = get_attribute_from_bases(bases, 'model_post_init')
|
| 396 |
+
if model_post_init is not BaseModel.model_post_init:
|
| 397 |
+
return model_post_init
|
| 398 |
+
|
| 399 |
+
|
| 400 |
+
def inspect_namespace( # noqa C901
|
| 401 |
+
namespace: dict[str, Any],
|
| 402 |
+
raw_annotations: dict[str, Any],
|
| 403 |
+
ignored_types: tuple[type[Any], ...],
|
| 404 |
+
base_class_vars: set[str],
|
| 405 |
+
base_class_fields: set[str],
|
| 406 |
+
) -> dict[str, ModelPrivateAttr]:
|
| 407 |
+
"""Iterate over the namespace and:
|
| 408 |
+
* gather private attributes
|
| 409 |
+
* check for items which look like fields but are not (e.g. have no annotation) and warn.
|
| 410 |
+
|
| 411 |
+
Args:
|
| 412 |
+
namespace: The attribute dictionary of the class to be created.
|
| 413 |
+
raw_annotations: The (non-evaluated) annotations of the model.
|
| 414 |
+
ignored_types: A tuple of ignore types.
|
| 415 |
+
base_class_vars: A set of base class class variables.
|
| 416 |
+
base_class_fields: A set of base class fields.
|
| 417 |
+
|
| 418 |
+
Returns:
|
| 419 |
+
A dict containing private attributes info.
|
| 420 |
+
|
| 421 |
+
Raises:
|
| 422 |
+
TypeError: If there is a `__root__` field in model.
|
| 423 |
+
NameError: If private attribute name is invalid.
|
| 424 |
+
PydanticUserError:
|
| 425 |
+
- If a field does not have a type annotation.
|
| 426 |
+
- If a field on base class was overridden by a non-annotated attribute.
|
| 427 |
+
"""
|
| 428 |
+
from ..fields import ModelPrivateAttr, PrivateAttr
|
| 429 |
+
|
| 430 |
+
FieldInfo = import_cached_field_info()
|
| 431 |
+
|
| 432 |
+
all_ignored_types = ignored_types + default_ignored_types()
|
| 433 |
+
|
| 434 |
+
private_attributes: dict[str, ModelPrivateAttr] = {}
|
| 435 |
+
|
| 436 |
+
if '__root__' in raw_annotations or '__root__' in namespace:
|
| 437 |
+
raise TypeError("To define root models, use `pydantic.RootModel` rather than a field called '__root__'")
|
| 438 |
+
|
| 439 |
+
ignored_names: set[str] = set()
|
| 440 |
+
for var_name, value in list(namespace.items()):
|
| 441 |
+
if var_name == 'model_config' or var_name == '__pydantic_extra__':
|
| 442 |
+
continue
|
| 443 |
+
elif (
|
| 444 |
+
isinstance(value, type)
|
| 445 |
+
and value.__module__ == namespace['__module__']
|
| 446 |
+
and '__qualname__' in namespace
|
| 447 |
+
and value.__qualname__.startswith(f'{namespace["__qualname__"]}.')
|
| 448 |
+
):
|
| 449 |
+
# `value` is a nested type defined in this namespace; don't error
|
| 450 |
+
continue
|
| 451 |
+
elif isinstance(value, all_ignored_types) or value.__class__.__module__ == 'functools':
|
| 452 |
+
ignored_names.add(var_name)
|
| 453 |
+
continue
|
| 454 |
+
elif isinstance(value, ModelPrivateAttr):
|
| 455 |
+
if var_name.startswith('__'):
|
| 456 |
+
raise NameError(
|
| 457 |
+
'Private attributes must not use dunder names;'
|
| 458 |
+
f' use a single underscore prefix instead of {var_name!r}.'
|
| 459 |
+
)
|
| 460 |
+
elif is_valid_field_name(var_name):
|
| 461 |
+
raise NameError(
|
| 462 |
+
'Private attributes must not use valid field names;'
|
| 463 |
+
f' use sunder names, e.g. {"_" + var_name!r} instead of {var_name!r}.'
|
| 464 |
+
)
|
| 465 |
+
private_attributes[var_name] = value
|
| 466 |
+
del namespace[var_name]
|
| 467 |
+
elif isinstance(value, FieldInfo) and not is_valid_field_name(var_name):
|
| 468 |
+
suggested_name = var_name.lstrip('_') or 'my_field' # don't suggest '' for all-underscore name
|
| 469 |
+
raise NameError(
|
| 470 |
+
f'Fields must not use names with leading underscores;'
|
| 471 |
+
f' e.g., use {suggested_name!r} instead of {var_name!r}.'
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
elif var_name.startswith('__'):
|
| 475 |
+
continue
|
| 476 |
+
elif is_valid_privateattr_name(var_name):
|
| 477 |
+
if var_name not in raw_annotations or not is_classvar_annotation(raw_annotations[var_name]):
|
| 478 |
+
private_attributes[var_name] = cast(ModelPrivateAttr, PrivateAttr(default=value))
|
| 479 |
+
del namespace[var_name]
|
| 480 |
+
elif var_name in base_class_vars:
|
| 481 |
+
continue
|
| 482 |
+
elif var_name not in raw_annotations:
|
| 483 |
+
if var_name in base_class_fields:
|
| 484 |
+
raise PydanticUserError(
|
| 485 |
+
f'Field {var_name!r} defined on a base class was overridden by a non-annotated attribute. '
|
| 486 |
+
f'All field definitions, including overrides, require a type annotation.',
|
| 487 |
+
code='model-field-overridden',
|
| 488 |
+
)
|
| 489 |
+
elif isinstance(value, FieldInfo):
|
| 490 |
+
raise PydanticUserError(
|
| 491 |
+
f'Field {var_name!r} requires a type annotation', code='model-field-missing-annotation'
|
| 492 |
+
)
|
| 493 |
+
else:
|
| 494 |
+
raise PydanticUserError(
|
| 495 |
+
f'A non-annotated attribute was detected: `{var_name} = {value!r}`. All model fields require a '
|
| 496 |
+
f'type annotation; if `{var_name}` is not meant to be a field, you may be able to resolve this '
|
| 497 |
+
f"error by annotating it as a `ClassVar` or updating `model_config['ignored_types']`.",
|
| 498 |
+
code='model-field-missing-annotation',
|
| 499 |
+
)
|
| 500 |
+
|
| 501 |
+
for ann_name, ann_type in raw_annotations.items():
|
| 502 |
+
if (
|
| 503 |
+
is_valid_privateattr_name(ann_name)
|
| 504 |
+
and ann_name not in private_attributes
|
| 505 |
+
and ann_name not in ignored_names
|
| 506 |
+
# This condition can be a false negative when `ann_type` is stringified,
|
| 507 |
+
# but it is handled in most cases in `set_model_fields`:
|
| 508 |
+
and not is_classvar_annotation(ann_type)
|
| 509 |
+
and ann_type not in all_ignored_types
|
| 510 |
+
and getattr(ann_type, '__module__', None) != 'functools'
|
| 511 |
+
):
|
| 512 |
+
if isinstance(ann_type, str):
|
| 513 |
+
# Walking up the frames to get the module namespace where the model is defined
|
| 514 |
+
# (as the model class wasn't created yet, we unfortunately can't use `cls.__module__`):
|
| 515 |
+
frame = sys._getframe(2)
|
| 516 |
+
if frame is not None:
|
| 517 |
+
try:
|
| 518 |
+
ann_type = eval_type_backport(
|
| 519 |
+
_make_forward_ref(ann_type, is_argument=False, is_class=True),
|
| 520 |
+
globalns=frame.f_globals,
|
| 521 |
+
localns=frame.f_locals,
|
| 522 |
+
)
|
| 523 |
+
except (NameError, TypeError):
|
| 524 |
+
pass
|
| 525 |
+
|
| 526 |
+
if typing_objects.is_annotated(get_origin(ann_type)):
|
| 527 |
+
_, *metadata = get_args(ann_type)
|
| 528 |
+
private_attr = next((v for v in metadata if isinstance(v, ModelPrivateAttr)), None)
|
| 529 |
+
if private_attr is not None:
|
| 530 |
+
private_attributes[ann_name] = private_attr
|
| 531 |
+
continue
|
| 532 |
+
private_attributes[ann_name] = PrivateAttr()
|
| 533 |
+
|
| 534 |
+
return private_attributes
|
| 535 |
+
|
| 536 |
+
|
| 537 |
+
def set_default_hash_func(cls: type[BaseModel], bases: tuple[type[Any], ...]) -> None:
|
| 538 |
+
base_hash_func = get_attribute_from_bases(bases, '__hash__')
|
| 539 |
+
new_hash_func = make_hash_func(cls)
|
| 540 |
+
if base_hash_func in {None, object.__hash__} or getattr(base_hash_func, '__code__', None) == new_hash_func.__code__:
|
| 541 |
+
# If `__hash__` is some default, we generate a hash function.
|
| 542 |
+
# It will be `None` if not overridden from BaseModel.
|
| 543 |
+
# It may be `object.__hash__` if there is another
|
| 544 |
+
# parent class earlier in the bases which doesn't override `__hash__` (e.g. `typing.Generic`).
|
| 545 |
+
# It may be a value set by `set_default_hash_func` if `cls` is a subclass of another frozen model.
|
| 546 |
+
# In the last case we still need a new hash function to account for new `model_fields`.
|
| 547 |
+
cls.__hash__ = new_hash_func
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
def make_hash_func(cls: type[BaseModel]) -> Any:
|
| 551 |
+
getter = operator.itemgetter(*cls.__pydantic_fields__.keys()) if cls.__pydantic_fields__ else lambda _: 0
|
| 552 |
+
|
| 553 |
+
def hash_func(self: Any) -> int:
|
| 554 |
+
try:
|
| 555 |
+
return hash(getter(self.__dict__))
|
| 556 |
+
except KeyError:
|
| 557 |
+
# In rare cases (such as when using the deprecated copy method), the __dict__ may not contain
|
| 558 |
+
# all model fields, which is how we can get here.
|
| 559 |
+
# getter(self.__dict__) is much faster than any 'safe' method that accounts for missing keys,
|
| 560 |
+
# and wrapping it in a `try` doesn't slow things down much in the common case.
|
| 561 |
+
return hash(getter(SafeGetItemProxy(self.__dict__)))
|
| 562 |
+
|
| 563 |
+
return hash_func
|
| 564 |
+
|
| 565 |
+
|
| 566 |
+
def set_model_fields(
|
| 567 |
+
cls: type[BaseModel],
|
| 568 |
+
config_wrapper: ConfigWrapper,
|
| 569 |
+
ns_resolver: NsResolver,
|
| 570 |
+
) -> None:
|
| 571 |
+
"""Collect and set `cls.__pydantic_fields__` and `cls.__class_vars__`.
|
| 572 |
+
|
| 573 |
+
Args:
|
| 574 |
+
cls: BaseModel or dataclass.
|
| 575 |
+
config_wrapper: The config wrapper instance.
|
| 576 |
+
ns_resolver: Namespace resolver to use when getting model annotations.
|
| 577 |
+
"""
|
| 578 |
+
typevars_map = get_model_typevars_map(cls)
|
| 579 |
+
fields, pydantic_extra_info, class_vars = collect_model_fields(
|
| 580 |
+
cls, config_wrapper, ns_resolver, typevars_map=typevars_map
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
cls.__pydantic_fields__ = fields
|
| 584 |
+
cls.__pydantic_extra_info__ = pydantic_extra_info
|
| 585 |
+
cls.__class_vars__.update(class_vars)
|
| 586 |
+
|
| 587 |
+
for k in class_vars:
|
| 588 |
+
# Class vars should not be private attributes
|
| 589 |
+
# We remove them _here_ and not earlier because we rely on inspecting the class to determine its classvars,
|
| 590 |
+
# but private attributes are determined by inspecting the namespace _prior_ to class creation.
|
| 591 |
+
# In the case that a classvar with a leading-'_' is defined via a ForwardRef (e.g., when using
|
| 592 |
+
# `__future__.annotations`), we want to remove the private attribute which was detected _before_ we knew it
|
| 593 |
+
# evaluated to a classvar
|
| 594 |
+
|
| 595 |
+
value = cls.__private_attributes__.pop(k, None)
|
| 596 |
+
if value is not None and value.default is not PydanticUndefined:
|
| 597 |
+
setattr(cls, k, value.default)
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
def complete_model_class(
|
| 601 |
+
cls: type[BaseModel],
|
| 602 |
+
config_wrapper: ConfigWrapper,
|
| 603 |
+
ns_resolver: NsResolver,
|
| 604 |
+
*,
|
| 605 |
+
raise_errors: bool = True,
|
| 606 |
+
call_on_complete_hook: bool = True,
|
| 607 |
+
create_model_module: str | None = None,
|
| 608 |
+
is_force_rebuild: bool = False,
|
| 609 |
+
) -> bool:
|
| 610 |
+
"""Finish building a model class.
|
| 611 |
+
|
| 612 |
+
This logic must be called after class has been created since validation functions must be bound
|
| 613 |
+
and `get_type_hints` requires a class object.
|
| 614 |
+
|
| 615 |
+
Args:
|
| 616 |
+
cls: BaseModel or dataclass.
|
| 617 |
+
config_wrapper: The config wrapper instance.
|
| 618 |
+
ns_resolver: The namespace resolver instance to use during schema building.
|
| 619 |
+
raise_errors: Whether to raise errors.
|
| 620 |
+
call_on_complete_hook: Whether to call the `__pydantic_on_complete__` hook.
|
| 621 |
+
create_model_module: The module of the class to be created, if created by `create_model`.
|
| 622 |
+
is_force_rebuild: Whether the model is being force-rebuilt (if True, pre-built serializers and
|
| 623 |
+
validators are not used, to avoid stale references).
|
| 624 |
+
|
| 625 |
+
Returns:
|
| 626 |
+
`True` if the model is successfully completed, else `False`.
|
| 627 |
+
|
| 628 |
+
Raises:
|
| 629 |
+
PydanticUndefinedAnnotation: If PydanticUndefinedAnnotation occurs in __get_pydantic_core_schema__
|
| 630 |
+
and `raise_errors=True`.
|
| 631 |
+
"""
|
| 632 |
+
typevars_map = get_model_typevars_map(cls)
|
| 633 |
+
|
| 634 |
+
if not cls.__pydantic_fields_complete__:
|
| 635 |
+
# Note: when coming from `ModelMetaclass.__new__()`, this results in fields being built twice.
|
| 636 |
+
# We do so a second time here so that we can get the ``NameError`` for the specific undefined annotation.
|
| 637 |
+
# Alternatively, we could let `GenerateSchema()` raise the error, but there are cases where incomplete
|
| 638 |
+
# fields are inherited in `collect_model_fields()` and can actually have their annotation resolved in the
|
| 639 |
+
# generate schema process. As we want to avoid having `__pydantic_fields_complete__` set to `False`
|
| 640 |
+
# when `__pydantic_complete__` is `True`, we rebuild here:
|
| 641 |
+
try:
|
| 642 |
+
cls.__pydantic_fields__, cls.__pydantic_extra_info__ = rebuild_model_fields(
|
| 643 |
+
cls,
|
| 644 |
+
config_wrapper=config_wrapper,
|
| 645 |
+
ns_resolver=ns_resolver,
|
| 646 |
+
typevars_map=typevars_map,
|
| 647 |
+
)
|
| 648 |
+
except NameError as e:
|
| 649 |
+
exc = PydanticUndefinedAnnotation.from_name_error(e)
|
| 650 |
+
set_model_mocks(cls, f'`{exc.name}`')
|
| 651 |
+
if raise_errors:
|
| 652 |
+
raise exc from e
|
| 653 |
+
|
| 654 |
+
if not raise_errors and not cls.__pydantic_fields_complete__:
|
| 655 |
+
# No need to continue with schema gen, it is guaranteed to fail
|
| 656 |
+
return False
|
| 657 |
+
|
| 658 |
+
assert cls.__pydantic_fields_complete__
|
| 659 |
+
|
| 660 |
+
gen_schema = GenerateSchema(
|
| 661 |
+
config_wrapper,
|
| 662 |
+
ns_resolver,
|
| 663 |
+
typevars_map,
|
| 664 |
+
)
|
| 665 |
+
|
| 666 |
+
try:
|
| 667 |
+
schema = gen_schema.generate_schema(cls)
|
| 668 |
+
except PydanticUndefinedAnnotation as e:
|
| 669 |
+
if raise_errors:
|
| 670 |
+
raise
|
| 671 |
+
set_model_mocks(cls, f'`{e.name}`')
|
| 672 |
+
return False
|
| 673 |
+
|
| 674 |
+
core_config = config_wrapper.core_config(title=cls.__name__)
|
| 675 |
+
|
| 676 |
+
try:
|
| 677 |
+
schema = gen_schema.clean_schema(schema)
|
| 678 |
+
except InvalidSchemaError:
|
| 679 |
+
set_model_mocks(cls)
|
| 680 |
+
return False
|
| 681 |
+
|
| 682 |
+
# This needs to happen *after* model schema generation, as the return types
|
| 683 |
+
# of the properties are evaluated and the `ComputedFieldInfo` are recreated:
|
| 684 |
+
cls.__pydantic_computed_fields__ = {k: v.info for k, v in cls.__pydantic_decorators__.computed_fields.items()}
|
| 685 |
+
|
| 686 |
+
set_deprecated_descriptors(cls)
|
| 687 |
+
|
| 688 |
+
cls.__pydantic_core_schema__ = schema
|
| 689 |
+
|
| 690 |
+
cls.__pydantic_validator__ = create_schema_validator(
|
| 691 |
+
schema,
|
| 692 |
+
cls,
|
| 693 |
+
create_model_module or cls.__module__,
|
| 694 |
+
cls.__qualname__,
|
| 695 |
+
'create_model' if create_model_module else 'BaseModel',
|
| 696 |
+
core_config,
|
| 697 |
+
config_wrapper.plugin_settings,
|
| 698 |
+
_use_prebuilt=not is_force_rebuild,
|
| 699 |
+
)
|
| 700 |
+
cls.__pydantic_serializer__ = SchemaSerializer(schema, core_config, _use_prebuilt=not is_force_rebuild)
|
| 701 |
+
|
| 702 |
+
# set __signature__ attr only for model class, but not for its instances
|
| 703 |
+
# (because instances can define `__call__`, and `inspect.signature` shouldn't
|
| 704 |
+
# use the `__signature__` attribute and instead generate from `__call__`).
|
| 705 |
+
cls.__signature__ = LazyClassAttribute(
|
| 706 |
+
'__signature__',
|
| 707 |
+
partial(
|
| 708 |
+
generate_pydantic_signature,
|
| 709 |
+
init=cls.__init__,
|
| 710 |
+
fields=cls.__pydantic_fields__,
|
| 711 |
+
validate_by_name=config_wrapper.validate_by_name,
|
| 712 |
+
extra=config_wrapper.extra,
|
| 713 |
+
),
|
| 714 |
+
)
|
| 715 |
+
|
| 716 |
+
cls.__pydantic_complete__ = True
|
| 717 |
+
|
| 718 |
+
if call_on_complete_hook:
|
| 719 |
+
cls.__pydantic_on_complete__()
|
| 720 |
+
|
| 721 |
+
return True
|
| 722 |
+
|
| 723 |
+
|
| 724 |
+
def set_deprecated_descriptors(cls: type[BaseModel]) -> None:
|
| 725 |
+
"""Set data descriptors on the class for deprecated fields."""
|
| 726 |
+
for field, field_info in cls.__pydantic_fields__.items():
|
| 727 |
+
if (msg := field_info.deprecation_message) is not None:
|
| 728 |
+
desc = _DeprecatedFieldDescriptor(msg)
|
| 729 |
+
desc.__set_name__(cls, field)
|
| 730 |
+
setattr(cls, field, desc)
|
| 731 |
+
|
| 732 |
+
for field, computed_field_info in cls.__pydantic_computed_fields__.items():
|
| 733 |
+
if (
|
| 734 |
+
(msg := computed_field_info.deprecation_message) is not None
|
| 735 |
+
# Avoid having two warnings emitted:
|
| 736 |
+
and not hasattr(unwrap_wrapped_function(computed_field_info.wrapped_property), '__deprecated__')
|
| 737 |
+
):
|
| 738 |
+
desc = _DeprecatedFieldDescriptor(msg, computed_field_info.wrapped_property)
|
| 739 |
+
desc.__set_name__(cls, field)
|
| 740 |
+
setattr(cls, field, desc)
|
| 741 |
+
|
| 742 |
+
|
| 743 |
+
class _DeprecatedFieldDescriptor:
|
| 744 |
+
"""Read-only data descriptor used to emit a runtime deprecation warning before accessing a deprecated field.
|
| 745 |
+
|
| 746 |
+
Attributes:
|
| 747 |
+
msg: The deprecation message to be emitted.
|
| 748 |
+
wrapped_property: The property instance if the deprecated field is a computed field, or `None`.
|
| 749 |
+
field_name: The name of the field being deprecated.
|
| 750 |
+
"""
|
| 751 |
+
|
| 752 |
+
field_name: str
|
| 753 |
+
|
| 754 |
+
def __init__(self, msg: str, wrapped_property: property | None = None) -> None:
|
| 755 |
+
self.msg = msg
|
| 756 |
+
self.wrapped_property = wrapped_property
|
| 757 |
+
|
| 758 |
+
def __set_name__(self, cls: type[BaseModel], name: str) -> None:
|
| 759 |
+
self.field_name = name
|
| 760 |
+
|
| 761 |
+
def __get__(self, obj: BaseModel | None, obj_type: type[BaseModel] | None = None) -> Any:
|
| 762 |
+
if obj is None:
|
| 763 |
+
if self.wrapped_property is not None:
|
| 764 |
+
return self.wrapped_property.__get__(None, obj_type)
|
| 765 |
+
raise AttributeError(self.field_name)
|
| 766 |
+
|
| 767 |
+
warnings.warn(self.msg, DeprecationWarning, stacklevel=2)
|
| 768 |
+
|
| 769 |
+
if self.wrapped_property is not None:
|
| 770 |
+
return self.wrapped_property.__get__(obj, obj_type)
|
| 771 |
+
return obj.__dict__[self.field_name]
|
| 772 |
+
|
| 773 |
+
# Defined to make it a data descriptor and take precedence over the instance's dictionary.
|
| 774 |
+
# Note that it will not be called when setting a value on a model instance
|
| 775 |
+
# as `BaseModel.__setattr__` is defined and takes priority.
|
| 776 |
+
def __set__(self, obj: Any, value: Any) -> NoReturn:
|
| 777 |
+
raise AttributeError(self.field_name)
|
| 778 |
+
|
| 779 |
+
|
| 780 |
+
class _PydanticWeakRef:
|
| 781 |
+
"""Wrapper for `weakref.ref` that enables `pickle` serialization.
|
| 782 |
+
|
| 783 |
+
Cloudpickle fails to serialize weakref.ref objects due to an arcane error related to
|
| 784 |
+
to abstract base classes (`abc.ABC`). This class works around the issue by wrapping
|
| 785 |
+
`weakref.ref` instead of subclassing it.
|
| 786 |
+
|
| 787 |
+
See https://github.com/pydantic/pydantic/issues/6763 for context.
|
| 788 |
+
|
| 789 |
+
Semantics:
|
| 790 |
+
- If not pickled, behaves the same as a `weakref.ref`.
|
| 791 |
+
- If pickled along with the referenced object, the same `weakref.ref` behavior
|
| 792 |
+
will be maintained between them after unpickling.
|
| 793 |
+
- If pickled without the referenced object, after unpickling the underlying
|
| 794 |
+
reference will be cleared (`__call__` will always return `None`).
|
| 795 |
+
"""
|
| 796 |
+
|
| 797 |
+
def __init__(self, obj: Any):
|
| 798 |
+
if obj is None:
|
| 799 |
+
# The object will be `None` upon deserialization if the serialized weakref
|
| 800 |
+
# had lost its underlying object.
|
| 801 |
+
self._wr = None
|
| 802 |
+
else:
|
| 803 |
+
self._wr = weakref.ref(obj)
|
| 804 |
+
|
| 805 |
+
def __call__(self) -> Any:
|
| 806 |
+
if self._wr is None:
|
| 807 |
+
return None
|
| 808 |
+
else:
|
| 809 |
+
return self._wr()
|
| 810 |
+
|
| 811 |
+
def __reduce__(self) -> tuple[Callable, tuple[weakref.ReferenceType | None]]:
|
| 812 |
+
return _PydanticWeakRef, (self(),)
|
| 813 |
+
|
| 814 |
+
|
| 815 |
+
def build_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | None:
|
| 816 |
+
"""Takes an input dictionary, and produces a new value that (invertibly) replaces the values with weakrefs.
|
| 817 |
+
|
| 818 |
+
We can't just use a WeakValueDictionary because many types (including int, str, etc.) can't be stored as values
|
| 819 |
+
in a WeakValueDictionary.
|
| 820 |
+
|
| 821 |
+
The `unpack_lenient_weakvaluedict` function can be used to reverse this operation.
|
| 822 |
+
"""
|
| 823 |
+
if d is None:
|
| 824 |
+
return None
|
| 825 |
+
result = {}
|
| 826 |
+
for k, v in d.items():
|
| 827 |
+
try:
|
| 828 |
+
proxy = _PydanticWeakRef(v)
|
| 829 |
+
except TypeError:
|
| 830 |
+
proxy = v
|
| 831 |
+
result[k] = proxy
|
| 832 |
+
return result
|
| 833 |
+
|
| 834 |
+
|
| 835 |
+
def unpack_lenient_weakvaluedict(d: dict[str, Any] | None) -> dict[str, Any] | None:
|
| 836 |
+
"""Inverts the transform performed by `build_lenient_weakvaluedict`."""
|
| 837 |
+
if d is None:
|
| 838 |
+
return None
|
| 839 |
+
|
| 840 |
+
result = {}
|
| 841 |
+
for k, v in d.items():
|
| 842 |
+
if isinstance(v, _PydanticWeakRef):
|
| 843 |
+
v = v()
|
| 844 |
+
if v is not None:
|
| 845 |
+
result[k] = v
|
| 846 |
+
else:
|
| 847 |
+
result[k] = v
|
| 848 |
+
return result
|
| 849 |
+
|
| 850 |
+
|
| 851 |
+
@cache
|
| 852 |
+
def default_ignored_types() -> tuple[type[Any], ...]:
|
| 853 |
+
from ..fields import ComputedFieldInfo
|
| 854 |
+
|
| 855 |
+
ignored_types = [
|
| 856 |
+
FunctionType,
|
| 857 |
+
property,
|
| 858 |
+
classmethod,
|
| 859 |
+
staticmethod,
|
| 860 |
+
PydanticDescriptorProxy,
|
| 861 |
+
ComputedFieldInfo,
|
| 862 |
+
TypeAliasType, # from `typing_extensions`
|
| 863 |
+
]
|
| 864 |
+
|
| 865 |
+
if sys.version_info >= (3, 12):
|
| 866 |
+
ignored_types.append(typing.TypeAliasType)
|
| 867 |
+
|
| 868 |
+
return tuple(ignored_types)
|